@salifbiaye/create-boutique 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/README.md +1 -0
  2. package/cli/dist/commands.js +382 -0
  3. package/cli/dist/create.js +194 -0
  4. package/cli/dist/docker.js +19 -0
  5. package/cli/dist/generate.js +322 -0
  6. package/cli/dist/guide.js +72 -0
  7. package/cli/dist/index.js +156 -0
  8. package/cli/dist/paths.js +29 -0
  9. package/cli/dist/ports.js +54 -0
  10. package/cli/dist/project.js +119 -0
  11. package/cli/dist/themes.js +24 -0
  12. package/docs/fiche-client.en.html +137 -0
  13. package/docs/fiche-client.html +137 -0
  14. package/docs/guide-complet.en.html +259 -0
  15. package/docs/guide-complet.fr.html +259 -0
  16. package/package.json +51 -0
  17. package/templates/backend/.dockerignore +9 -0
  18. package/templates/backend/Dockerfile +30 -0
  19. package/templates/backend/README.md +78 -0
  20. package/templates/backend/TEMPLATE_VERSION +1 -0
  21. package/templates/backend/docker/entrypoint.sh +28 -0
  22. package/templates/backend/instrumentation.ts +24 -0
  23. package/templates/backend/medusa-config.ts +114 -0
  24. package/templates/backend/package-lock.json +16222 -0
  25. package/templates/backend/package.json +39 -0
  26. package/templates/backend/src/admin/README.md +33 -0
  27. package/templates/backend/src/admin/components/image-field.tsx +61 -0
  28. package/templates/backend/src/admin/i18n/README.md +58 -0
  29. package/templates/backend/src/admin/i18n/index.ts +8 -0
  30. package/templates/backend/src/admin/i18n/json/en.json +66 -0
  31. package/templates/backend/src/admin/i18n/json/fr.json +66 -0
  32. package/templates/backend/src/admin/lib/client.ts +9 -0
  33. package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +286 -0
  34. package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +113 -0
  35. package/templates/backend/src/admin/tsconfig.json +24 -0
  36. package/templates/backend/src/admin/vite-env.d.ts +1 -0
  37. package/templates/backend/src/admin/widgets/category-image.tsx +56 -0
  38. package/templates/backend/src/api/README.md +135 -0
  39. package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +30 -0
  40. package/templates/backend/src/api/admin/boutique-campaigns/route.ts +24 -0
  41. package/templates/backend/src/api/middlewares.ts +28 -0
  42. package/templates/backend/src/api/rate-limit.ts +30 -0
  43. package/templates/backend/src/api/route.ts +6 -0
  44. package/templates/backend/src/api/store/campaign/route.ts +25 -0
  45. package/templates/backend/src/api/store/order-lookup/route.ts +74 -0
  46. package/templates/backend/src/emails/en.json +64 -0
  47. package/templates/backend/src/emails/fr.json +64 -0
  48. package/templates/backend/src/jobs/README.md +38 -0
  49. package/templates/backend/src/lib/emails.ts +94 -0
  50. package/templates/backend/src/lib/notify.ts +148 -0
  51. package/templates/backend/src/lib/project.ts +146 -0
  52. package/templates/backend/src/links/README.md +26 -0
  53. package/templates/backend/src/links/campaign-sales-channel.ts +9 -0
  54. package/templates/backend/src/modules/README.md +117 -0
  55. package/templates/backend/src/modules/campaign/index.ts +9 -0
  56. package/templates/backend/src/modules/campaign/migrations/.snapshot-boutique-campaign.json +272 -0
  57. package/templates/backend/src/modules/campaign/migrations/Migration20260928141440.ts +16 -0
  58. package/templates/backend/src/modules/campaign/migrations/Migration20260928145243.ts +13 -0
  59. package/templates/backend/src/modules/campaign/models/campaign.ts +28 -0
  60. package/templates/backend/src/modules/campaign/service.ts +6 -0
  61. package/templates/backend/src/modules/campaign/validators.ts +94 -0
  62. package/templates/backend/src/modules/resend/index.ts +6 -0
  63. package/templates/backend/src/modules/resend/service.ts +63 -0
  64. package/templates/backend/src/scripts/README.md +63 -0
  65. package/templates/backend/src/scripts/campaign-export.ts +15 -0
  66. package/templates/backend/src/scripts/campaign-import.ts +38 -0
  67. package/templates/backend/src/scripts/price-report.ts +34 -0
  68. package/templates/backend/src/scripts/seed.ts +418 -0
  69. package/templates/backend/src/scripts/storage.ts +63 -0
  70. package/templates/backend/src/subscribers/README.md +61 -0
  71. package/templates/backend/src/subscribers/account-emails.ts +27 -0
  72. package/templates/backend/src/subscribers/fulfillment-emails.ts +42 -0
  73. package/templates/backend/src/subscribers/order-emails.ts +12 -0
  74. package/templates/backend/src/subscribers/return-refund-emails.ts +50 -0
  75. package/templates/backend/src/workflows/README.md +81 -0
  76. package/templates/backend/src/workflows/steps/upsert-campaign.ts +53 -0
  77. package/templates/backend/src/workflows/upsert-campaign.ts +30 -0
  78. package/templates/backend/tsconfig.json +36 -0
  79. package/templates/storefront/.dockerignore +7 -0
  80. package/templates/storefront/.github/ISSUE_TEMPLATE/bug-report.yml +71 -0
  81. package/templates/storefront/.prettierrc +8 -0
  82. package/templates/storefront/Dockerfile +37 -0
  83. package/templates/storefront/LICENSE +21 -0
  84. package/templates/storefront/README.md +127 -0
  85. package/templates/storefront/locales/en.json +269 -0
  86. package/templates/storefront/locales/fr.json +269 -0
  87. package/templates/storefront/next-env.d.ts +7 -0
  88. package/templates/storefront/next-sitemap.js +19 -0
  89. package/templates/storefront/next.config.js +29 -0
  90. package/templates/storefront/package-lock.json +9031 -0
  91. package/templates/storefront/package.json +49 -0
  92. package/templates/storefront/postcss.config.js +6 -0
  93. package/templates/storefront/public/favicon.ico +0 -0
  94. package/templates/storefront/scripts/sync-design.mjs +30 -0
  95. package/templates/storefront/src/app/(checkout)/checkout/page.tsx +31 -0
  96. package/templates/storefront/src/app/(checkout)/layout.tsx +29 -0
  97. package/templates/storefront/src/app/(checkout)/not-found.tsx +20 -0
  98. package/templates/storefront/src/app/(main)/account/@dashboard/addresses/page.tsx +34 -0
  99. package/templates/storefront/src/app/(main)/account/@dashboard/loading.tsx +9 -0
  100. package/templates/storefront/src/app/(main)/account/@dashboard/orders/details/[id]/page.tsx +33 -0
  101. package/templates/storefront/src/app/(main)/account/@dashboard/orders/page.tsx +37 -0
  102. package/templates/storefront/src/app/(main)/account/@dashboard/page.tsx +22 -0
  103. package/templates/storefront/src/app/(main)/account/@dashboard/profile/page.tsx +53 -0
  104. package/templates/storefront/src/app/(main)/account/@login/page.tsx +12 -0
  105. package/templates/storefront/src/app/(main)/account/layout.tsx +20 -0
  106. package/templates/storefront/src/app/(main)/account/loading.tsx +9 -0
  107. package/templates/storefront/src/app/(main)/cart/loading.tsx +5 -0
  108. package/templates/storefront/src/app/(main)/cart/not-found.tsx +21 -0
  109. package/templates/storefront/src/app/(main)/cart/page.tsx +21 -0
  110. package/templates/storefront/src/app/(main)/categories/[...category]/page.tsx +55 -0
  111. package/templates/storefront/src/app/(main)/collections/[handle]/page.tsx +55 -0
  112. package/templates/storefront/src/app/(main)/layout.tsx +43 -0
  113. package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -0
  114. package/templates/storefront/src/app/(main)/not-found.tsx +21 -0
  115. package/templates/storefront/src/app/(main)/order/[id]/confirmed/loading.tsx +5 -0
  116. package/templates/storefront/src/app/(main)/order/[id]/confirmed/page.tsx +23 -0
  117. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/accept/page.tsx +42 -0
  118. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/decline/page.tsx +42 -0
  119. package/templates/storefront/src/app/(main)/order/[id]/transfer/[token]/page.tsx +35 -0
  120. package/templates/storefront/src/app/(main)/order/track/page.tsx +32 -0
  121. package/templates/storefront/src/app/(main)/page.tsx +68 -0
  122. package/templates/storefront/src/app/(main)/products/[handle]/page.tsx +86 -0
  123. package/templates/storefront/src/app/(main)/store/page.tsx +28 -0
  124. package/templates/storefront/src/app/api/health/route.ts +13 -0
  125. package/templates/storefront/src/app/layout.tsx +42 -0
  126. package/templates/storefront/src/app/not-found.tsx +21 -0
  127. package/templates/storefront/src/app/opengraph-image.jpg +0 -0
  128. package/templates/storefront/src/app/twitter-image.jpg +0 -0
  129. package/templates/storefront/src/instrumentation-node.ts +18 -0
  130. package/templates/storefront/src/instrumentation.ts +9 -0
  131. package/templates/storefront/src/lib/boutique/campaign.ts +116 -0
  132. package/templates/storefront/src/lib/boutique/config.ts +152 -0
  133. package/templates/storefront/src/lib/boutique/fonts.ts +29 -0
  134. package/templates/storefront/src/lib/boutique/i18n-client.tsx +34 -0
  135. package/templates/storefront/src/lib/boutique/i18n.ts +43 -0
  136. package/templates/storefront/src/lib/boutique/schema.ts +131 -0
  137. package/templates/storefront/src/lib/config.ts +39 -0
  138. package/templates/storefront/src/lib/constants.tsx +75 -0
  139. package/templates/storefront/src/lib/context/modal-context.tsx +34 -0
  140. package/templates/storefront/src/lib/data/cart.ts +434 -0
  141. package/templates/storefront/src/lib/data/categories.ts +49 -0
  142. package/templates/storefront/src/lib/data/collections.ts +59 -0
  143. package/templates/storefront/src/lib/data/cookies.ts +103 -0
  144. package/templates/storefront/src/lib/data/customer.ts +261 -0
  145. package/templates/storefront/src/lib/data/fulfillment.ts +68 -0
  146. package/templates/storefront/src/lib/data/onboarding.ts +9 -0
  147. package/templates/storefront/src/lib/data/order-lookup.ts +34 -0
  148. package/templates/storefront/src/lib/data/orders.ts +112 -0
  149. package/templates/storefront/src/lib/data/payment.ts +35 -0
  150. package/templates/storefront/src/lib/data/products.ts +127 -0
  151. package/templates/storefront/src/lib/data/regions.ts +70 -0
  152. package/templates/storefront/src/lib/data/variants.ts +38 -0
  153. package/templates/storefront/src/lib/hooks/use-in-view.tsx +29 -0
  154. package/templates/storefront/src/lib/hooks/use-toggle-state.tsx +46 -0
  155. package/templates/storefront/src/lib/util/compare-addresses.ts +28 -0
  156. package/templates/storefront/src/lib/util/get-percentage-diff.ts +6 -0
  157. package/templates/storefront/src/lib/util/get-product-price.ts +79 -0
  158. package/templates/storefront/src/lib/util/isEmpty.ts +11 -0
  159. package/templates/storefront/src/lib/util/medusa-error.ts +22 -0
  160. package/templates/storefront/src/lib/util/money.ts +35 -0
  161. package/templates/storefront/src/lib/util/product.ts +5 -0
  162. package/templates/storefront/src/lib/util/repeat.ts +5 -0
  163. package/templates/storefront/src/lib/util/sort-products.ts +50 -0
  164. package/templates/storefront/src/modules/account/components/account-info/index.tsx +140 -0
  165. package/templates/storefront/src/modules/account/components/account-nav/index.tsx +198 -0
  166. package/templates/storefront/src/modules/account/components/address-book/index.tsx +28 -0
  167. package/templates/storefront/src/modules/account/components/address-card/add-address.tsx +169 -0
  168. package/templates/storefront/src/modules/account/components/address-card/edit-address-modal.tsx +241 -0
  169. package/templates/storefront/src/modules/account/components/login/index.tsx +65 -0
  170. package/templates/storefront/src/modules/account/components/order-card/index.tsx +88 -0
  171. package/templates/storefront/src/modules/account/components/order-overview/index.tsx +47 -0
  172. package/templates/storefront/src/modules/account/components/overview/index.tsx +169 -0
  173. package/templates/storefront/src/modules/account/components/profile-billing-address/index.tsx +193 -0
  174. package/templates/storefront/src/modules/account/components/profile-email/index.tsx +75 -0
  175. package/templates/storefront/src/modules/account/components/profile-name/index.tsx +79 -0
  176. package/templates/storefront/src/modules/account/components/profile-password/index.tsx +72 -0
  177. package/templates/storefront/src/modules/account/components/profile-phone/index.tsx +74 -0
  178. package/templates/storefront/src/modules/account/components/register/index.tsx +107 -0
  179. package/templates/storefront/src/modules/account/components/transfer-request-form/index.tsx +83 -0
  180. package/templates/storefront/src/modules/account/templates/account-layout.tsx +43 -0
  181. package/templates/storefront/src/modules/account/templates/login-template.tsx +27 -0
  182. package/templates/storefront/src/modules/cart/components/cart-item-select/index.tsx +75 -0
  183. package/templates/storefront/src/modules/cart/components/empty-cart-message/index.tsx +25 -0
  184. package/templates/storefront/src/modules/cart/components/item/index.tsx +144 -0
  185. package/templates/storefront/src/modules/cart/components/sign-in-prompt/index.tsx +27 -0
  186. package/templates/storefront/src/modules/cart/templates/index.tsx +51 -0
  187. package/templates/storefront/src/modules/cart/templates/items.tsx +58 -0
  188. package/templates/storefront/src/modules/cart/templates/preview.tsx +51 -0
  189. package/templates/storefront/src/modules/cart/templates/summary.tsx +50 -0
  190. package/templates/storefront/src/modules/categories/templates/index.tsx +94 -0
  191. package/templates/storefront/src/modules/checkout/components/address-select/index.tsx +117 -0
  192. package/templates/storefront/src/modules/checkout/components/addresses/index.tsx +186 -0
  193. package/templates/storefront/src/modules/checkout/components/billing_address/index.tsx +114 -0
  194. package/templates/storefront/src/modules/checkout/components/country-select/index.tsx +50 -0
  195. package/templates/storefront/src/modules/checkout/components/discount-code/index.tsx +181 -0
  196. package/templates/storefront/src/modules/checkout/components/error-message/index.tsx +13 -0
  197. package/templates/storefront/src/modules/checkout/components/payment/index.tsx +261 -0
  198. package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +195 -0
  199. package/templates/storefront/src/modules/checkout/components/payment-container/index.tsx +130 -0
  200. package/templates/storefront/src/modules/checkout/components/payment-test/index.tsx +12 -0
  201. package/templates/storefront/src/modules/checkout/components/payment-wrapper/index.tsx +42 -0
  202. package/templates/storefront/src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx +54 -0
  203. package/templates/storefront/src/modules/checkout/components/review/index.tsx +54 -0
  204. package/templates/storefront/src/modules/checkout/components/shipping/index.tsx +417 -0
  205. package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +219 -0
  206. package/templates/storefront/src/modules/checkout/components/submit-button/index.tsx +32 -0
  207. package/templates/storefront/src/modules/checkout/templates/checkout-form/index.tsx +38 -0
  208. package/templates/storefront/src/modules/checkout/templates/checkout-summary/index.tsx +31 -0
  209. package/templates/storefront/src/modules/collections/templates/index.tsx +44 -0
  210. package/templates/storefront/src/modules/common/components/cart-totals/index.tsx +84 -0
  211. package/templates/storefront/src/modules/common/components/checkbox/index.tsx +44 -0
  212. package/templates/storefront/src/modules/common/components/delete-button/index.tsx +42 -0
  213. package/templates/storefront/src/modules/common/components/divider/index.tsx +9 -0
  214. package/templates/storefront/src/modules/common/components/filter-radio-group/index.tsx +61 -0
  215. package/templates/storefront/src/modules/common/components/input/index.tsx +79 -0
  216. package/templates/storefront/src/modules/common/components/interactive-link/index.tsx +33 -0
  217. package/templates/storefront/src/modules/common/components/line-item-options/index.tsx +26 -0
  218. package/templates/storefront/src/modules/common/components/line-item-price/index.tsx +63 -0
  219. package/templates/storefront/src/modules/common/components/line-item-unit-price/index.tsx +62 -0
  220. package/templates/storefront/src/modules/common/components/localized-client-link/index.tsx +29 -0
  221. package/templates/storefront/src/modules/common/components/modal/index.tsx +118 -0
  222. package/templates/storefront/src/modules/common/components/native-select/index.tsx +76 -0
  223. package/templates/storefront/src/modules/common/components/radio/index.tsx +27 -0
  224. package/templates/storefront/src/modules/common/icons/back.tsx +37 -0
  225. package/templates/storefront/src/modules/common/icons/bancontact.tsx +26 -0
  226. package/templates/storefront/src/modules/common/icons/chevron-down.tsx +30 -0
  227. package/templates/storefront/src/modules/common/icons/eye-off.tsx +37 -0
  228. package/templates/storefront/src/modules/common/icons/eye.tsx +37 -0
  229. package/templates/storefront/src/modules/common/icons/fast-delivery.tsx +65 -0
  230. package/templates/storefront/src/modules/common/icons/ideal.tsx +26 -0
  231. package/templates/storefront/src/modules/common/icons/map-pin.tsx +37 -0
  232. package/templates/storefront/src/modules/common/icons/package.tsx +44 -0
  233. package/templates/storefront/src/modules/common/icons/paypal.tsx +30 -0
  234. package/templates/storefront/src/modules/common/icons/placeholder-image.tsx +44 -0
  235. package/templates/storefront/src/modules/common/icons/refresh.tsx +51 -0
  236. package/templates/storefront/src/modules/common/icons/spinner.tsx +37 -0
  237. package/templates/storefront/src/modules/common/icons/trash.tsx +51 -0
  238. package/templates/storefront/src/modules/common/icons/user.tsx +37 -0
  239. package/templates/storefront/src/modules/common/icons/x.tsx +37 -0
  240. package/templates/storefront/src/modules/layout/components/cart-mismatch-banner/index.tsx +59 -0
  241. package/templates/storefront/src/modules/order/components/help/index.tsx +26 -0
  242. package/templates/storefront/src/modules/order/components/item/index.tsx +57 -0
  243. package/templates/storefront/src/modules/order/components/items/index.tsx +44 -0
  244. package/templates/storefront/src/modules/order/components/onboarding-cta/index.tsx +28 -0
  245. package/templates/storefront/src/modules/order/components/order-details/index.tsx +64 -0
  246. package/templates/storefront/src/modules/order/components/order-summary/index.tsx +61 -0
  247. package/templates/storefront/src/modules/order/components/payment-details/index.tsx +64 -0
  248. package/templates/storefront/src/modules/order/components/shipping-details/index.tsx +74 -0
  249. package/templates/storefront/src/modules/order/components/transfer-actions/index.tsx +83 -0
  250. package/templates/storefront/src/modules/order/components/transfer-image/index.tsx +275 -0
  251. package/templates/storefront/src/modules/order/templates/order-completed-template.tsx +53 -0
  252. package/templates/storefront/src/modules/order/templates/order-details-template.tsx +46 -0
  253. package/templates/storefront/src/modules/products/components/thumbnail/index.tsx +70 -0
  254. package/templates/storefront/src/modules/products/templates/index.tsx +118 -0
  255. package/templates/storefront/src/modules/shipping/components/free-shipping-price-nudge/index.tsx +285 -0
  256. package/templates/storefront/src/modules/skeletons/components/skeleton-button/index.tsx +5 -0
  257. package/templates/storefront/src/modules/skeletons/components/skeleton-card-details/index.tsx +10 -0
  258. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-item/index.tsx +35 -0
  259. package/templates/storefront/src/modules/skeletons/components/skeleton-cart-totals/index.tsx +30 -0
  260. package/templates/storefront/src/modules/skeletons/components/skeleton-code-form/index.tsx +13 -0
  261. package/templates/storefront/src/modules/skeletons/components/skeleton-line-item/index.tsx +35 -0
  262. package/templates/storefront/src/modules/skeletons/components/skeleton-order-confirmed-header/index.tsx +14 -0
  263. package/templates/storefront/src/modules/skeletons/components/skeleton-order-information/index.tsx +36 -0
  264. package/templates/storefront/src/modules/skeletons/components/skeleton-order-items/index.tsx +43 -0
  265. package/templates/storefront/src/modules/skeletons/components/skeleton-order-summary/index.tsx +15 -0
  266. package/templates/storefront/src/modules/skeletons/components/skeleton-product-preview/index.tsx +15 -0
  267. package/templates/storefront/src/modules/skeletons/templates/skeleton-cart-page/index.tsx +65 -0
  268. package/templates/storefront/src/modules/skeletons/templates/skeleton-order-confirmed/index.tsx +21 -0
  269. package/templates/storefront/src/modules/skeletons/templates/skeleton-product-grid/index.tsx +23 -0
  270. package/templates/storefront/src/modules/store/components/pagination/index.tsx +114 -0
  271. package/templates/storefront/src/modules/store/components/refinement-list/index.tsx +41 -0
  272. package/templates/storefront/src/modules/store/components/refinement-list/sort-products/index.tsx +48 -0
  273. package/templates/storefront/src/modules/store/templates/index.tsx +42 -0
  274. package/templates/storefront/src/modules/store/templates/paginated-products.tsx +80 -0
  275. package/templates/storefront/src/modules/ub/button.tsx +41 -0
  276. package/templates/storefront/src/modules/ub/campaign-blocks.tsx +197 -0
  277. package/templates/storefront/src/modules/ub/campaign-ended.tsx +28 -0
  278. package/templates/storefront/src/modules/ub/category-tiles.tsx +41 -0
  279. package/templates/storefront/src/modules/ub/countdown.tsx +46 -0
  280. package/templates/storefront/src/modules/ub/footer.tsx +56 -0
  281. package/templates/storefront/src/modules/ub/icons.tsx +42 -0
  282. package/templates/storefront/src/modules/ub/nav.tsx +90 -0
  283. package/templates/storefront/src/modules/ub/pdp/buy-box.tsx +140 -0
  284. package/templates/storefront/src/modules/ub/pdp/gallery.tsx +40 -0
  285. package/templates/storefront/src/modules/ub/product-card.tsx +66 -0
  286. package/templates/storefront/src/modules/ub/top-bar.tsx +20 -0
  287. package/templates/storefront/src/modules/ub/track-form.tsx +92 -0
  288. package/templates/storefront/src/proxy.ts +89 -0
  289. package/templates/storefront/src/styles/globals.css +98 -0
  290. package/templates/storefront/src/styles/ub/bundle.css +372 -0
  291. package/templates/storefront/src/styles/ub/fonts.css +17 -0
  292. package/templates/storefront/src/styles/ub/medusa-ui.css +90 -0
  293. package/templates/storefront/src/styles/ub/overrides.css +12 -0
  294. package/templates/storefront/src/styles/ub/tokens.css +9 -0
  295. package/templates/storefront/src/types/global.ts +24 -0
  296. package/templates/storefront/src/types/icon.ts +4 -0
  297. package/templates/storefront/tailwind.config.js +164 -0
  298. package/templates/storefront/themes/black-friday/campaign.en.json +31 -0
  299. package/templates/storefront/themes/black-friday/campaign.fr.json +31 -0
  300. package/templates/storefront/themes/black-friday/theme.json +9 -0
  301. package/templates/storefront/themes/cyber-monday/campaign.en.json +31 -0
  302. package/templates/storefront/themes/cyber-monday/campaign.fr.json +31 -0
  303. package/templates/storefront/themes/cyber-monday/theme.json +9 -0
  304. package/templates/storefront/themes/halloween/campaign.en.json +31 -0
  305. package/templates/storefront/themes/halloween/campaign.fr.json +31 -0
  306. package/templates/storefront/themes/halloween/theme.json +9 -0
  307. package/templates/storefront/themes/noel/campaign.en.json +31 -0
  308. package/templates/storefront/themes/noel/campaign.fr.json +31 -0
  309. package/templates/storefront/themes/noel/theme.json +9 -0
  310. package/templates/storefront/themes/nouvel-an/campaign.en.json +31 -0
  311. package/templates/storefront/themes/nouvel-an/campaign.fr.json +31 -0
  312. package/templates/storefront/themes/nouvel-an/theme.json +9 -0
  313. package/templates/storefront/themes/thanksgiving/campaign.en.json +31 -0
  314. package/templates/storefront/themes/thanksgiving/campaign.fr.json +31 -0
  315. package/templates/storefront/themes/thanksgiving/theme.json +9 -0
  316. package/templates/storefront/tsconfig.json +53 -0
@@ -0,0 +1,272 @@
1
+ {
2
+ "namespaces": [
3
+ "public"
4
+ ],
5
+ "name": "public",
6
+ "tables": [
7
+ {
8
+ "columns": {
9
+ "id": {
10
+ "name": "id",
11
+ "type": "text",
12
+ "unsigned": false,
13
+ "autoincrement": false,
14
+ "primary": true,
15
+ "nullable": false,
16
+ "unique": false,
17
+ "length": null,
18
+ "precision": null,
19
+ "scale": null,
20
+ "default": null,
21
+ "comment": null,
22
+ "enumItems": [],
23
+ "mappedType": "text"
24
+ },
25
+ "sales_channel_id": {
26
+ "name": "sales_channel_id",
27
+ "type": "text",
28
+ "unsigned": false,
29
+ "autoincrement": false,
30
+ "primary": false,
31
+ "nullable": false,
32
+ "unique": false,
33
+ "length": null,
34
+ "precision": null,
35
+ "scale": null,
36
+ "default": null,
37
+ "comment": null,
38
+ "enumItems": [],
39
+ "mappedType": "text"
40
+ },
41
+ "starts_at": {
42
+ "name": "starts_at",
43
+ "type": "timestamptz",
44
+ "unsigned": false,
45
+ "autoincrement": false,
46
+ "primary": false,
47
+ "nullable": false,
48
+ "unique": false,
49
+ "length": 6,
50
+ "precision": null,
51
+ "scale": null,
52
+ "default": null,
53
+ "comment": null,
54
+ "enumItems": [],
55
+ "mappedType": "datetime"
56
+ },
57
+ "ends_at": {
58
+ "name": "ends_at",
59
+ "type": "timestamptz",
60
+ "unsigned": false,
61
+ "autoincrement": false,
62
+ "primary": false,
63
+ "nullable": false,
64
+ "unique": false,
65
+ "length": 6,
66
+ "precision": null,
67
+ "scale": null,
68
+ "default": null,
69
+ "comment": null,
70
+ "enumItems": [],
71
+ "mappedType": "datetime"
72
+ },
73
+ "next_campaign": {
74
+ "name": "next_campaign",
75
+ "type": "text",
76
+ "unsigned": false,
77
+ "autoincrement": false,
78
+ "primary": false,
79
+ "nullable": true,
80
+ "unique": false,
81
+ "length": null,
82
+ "precision": null,
83
+ "scale": null,
84
+ "default": null,
85
+ "comment": null,
86
+ "enumItems": [],
87
+ "mappedType": "text"
88
+ },
89
+ "texts": {
90
+ "name": "texts",
91
+ "type": "jsonb",
92
+ "unsigned": false,
93
+ "autoincrement": false,
94
+ "primary": false,
95
+ "nullable": true,
96
+ "unique": false,
97
+ "length": null,
98
+ "precision": null,
99
+ "scale": null,
100
+ "default": null,
101
+ "comment": null,
102
+ "enumItems": [],
103
+ "mappedType": "json"
104
+ },
105
+ "vars": {
106
+ "name": "vars",
107
+ "type": "jsonb",
108
+ "unsigned": false,
109
+ "autoincrement": false,
110
+ "primary": false,
111
+ "nullable": true,
112
+ "unique": false,
113
+ "length": null,
114
+ "precision": null,
115
+ "scale": null,
116
+ "default": null,
117
+ "comment": null,
118
+ "enumItems": [],
119
+ "mappedType": "json"
120
+ },
121
+ "hero_image": {
122
+ "name": "hero_image",
123
+ "type": "text",
124
+ "unsigned": false,
125
+ "autoincrement": false,
126
+ "primary": false,
127
+ "nullable": true,
128
+ "unique": false,
129
+ "length": null,
130
+ "precision": null,
131
+ "scale": null,
132
+ "default": null,
133
+ "comment": null,
134
+ "enumItems": [],
135
+ "mappedType": "text"
136
+ },
137
+ "logo": {
138
+ "name": "logo",
139
+ "type": "text",
140
+ "unsigned": false,
141
+ "autoincrement": false,
142
+ "primary": false,
143
+ "nullable": true,
144
+ "unique": false,
145
+ "length": null,
146
+ "precision": null,
147
+ "scale": null,
148
+ "default": null,
149
+ "comment": null,
150
+ "enumItems": [],
151
+ "mappedType": "text"
152
+ },
153
+ "promo_image": {
154
+ "name": "promo_image",
155
+ "type": "text",
156
+ "unsigned": false,
157
+ "autoincrement": false,
158
+ "primary": false,
159
+ "nullable": true,
160
+ "unique": false,
161
+ "length": null,
162
+ "precision": null,
163
+ "scale": null,
164
+ "default": null,
165
+ "comment": null,
166
+ "enumItems": [],
167
+ "mappedType": "text"
168
+ },
169
+ "show_countdown": {
170
+ "name": "show_countdown",
171
+ "type": "boolean",
172
+ "unsigned": false,
173
+ "autoincrement": false,
174
+ "primary": false,
175
+ "nullable": true,
176
+ "unique": false,
177
+ "length": null,
178
+ "precision": null,
179
+ "scale": null,
180
+ "default": null,
181
+ "comment": null,
182
+ "enumItems": [],
183
+ "mappedType": "boolean"
184
+ },
185
+ "created_at": {
186
+ "name": "created_at",
187
+ "type": "timestamptz",
188
+ "unsigned": false,
189
+ "autoincrement": false,
190
+ "primary": false,
191
+ "nullable": false,
192
+ "unique": false,
193
+ "length": 6,
194
+ "precision": null,
195
+ "scale": null,
196
+ "default": "now()",
197
+ "comment": null,
198
+ "enumItems": [],
199
+ "mappedType": "datetime"
200
+ },
201
+ "updated_at": {
202
+ "name": "updated_at",
203
+ "type": "timestamptz",
204
+ "unsigned": false,
205
+ "autoincrement": false,
206
+ "primary": false,
207
+ "nullable": false,
208
+ "unique": false,
209
+ "length": 6,
210
+ "precision": null,
211
+ "scale": null,
212
+ "default": "now()",
213
+ "comment": null,
214
+ "enumItems": [],
215
+ "mappedType": "datetime"
216
+ },
217
+ "deleted_at": {
218
+ "name": "deleted_at",
219
+ "type": "timestamptz",
220
+ "unsigned": false,
221
+ "autoincrement": false,
222
+ "primary": false,
223
+ "nullable": true,
224
+ "unique": false,
225
+ "length": 6,
226
+ "precision": null,
227
+ "scale": null,
228
+ "default": null,
229
+ "comment": null,
230
+ "enumItems": [],
231
+ "mappedType": "datetime"
232
+ }
233
+ },
234
+ "name": "boutique_campaign",
235
+ "schema": "public",
236
+ "indexes": [
237
+ {
238
+ "keyName": "IDX_boutique_campaign_sales_channel_id_unique",
239
+ "columnNames": [],
240
+ "composite": false,
241
+ "constraint": false,
242
+ "primary": false,
243
+ "unique": false,
244
+ "expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_boutique_campaign_sales_channel_id_unique\" ON \"boutique_campaign\" (\"sales_channel_id\") WHERE deleted_at IS NULL"
245
+ },
246
+ {
247
+ "keyName": "IDX_boutique_campaign_deleted_at",
248
+ "columnNames": [],
249
+ "composite": false,
250
+ "constraint": false,
251
+ "primary": false,
252
+ "unique": false,
253
+ "expression": "CREATE INDEX IF NOT EXISTS \"IDX_boutique_campaign_deleted_at\" ON \"boutique_campaign\" (\"deleted_at\") WHERE deleted_at IS NULL"
254
+ },
255
+ {
256
+ "keyName": "boutique_campaign_pkey",
257
+ "columnNames": [
258
+ "id"
259
+ ],
260
+ "composite": false,
261
+ "constraint": true,
262
+ "primary": true,
263
+ "unique": true
264
+ }
265
+ ],
266
+ "checks": [],
267
+ "foreignKeys": {},
268
+ "nativeEnums": {}
269
+ }
270
+ ],
271
+ "nativeEnums": {}
272
+ }
@@ -0,0 +1,16 @@
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
+
3
+ export class Migration20260928141440 extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`alter table if exists "boutique_campaign" drop constraint if exists "boutique_campaign_sales_channel_id_unique";`);
7
+ this.addSql(`create table if not exists "boutique_campaign" ("id" text not null, "sales_channel_id" text not null, "starts_at" timestamptz not null, "ends_at" timestamptz not null, "next_campaign" text null, "texts" jsonb null, "vars" jsonb null, "hero_image" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "boutique_campaign_pkey" primary key ("id"));`);
8
+ this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_boutique_campaign_sales_channel_id_unique" ON "boutique_campaign" ("sales_channel_id") WHERE deleted_at IS NULL;`);
9
+ this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_boutique_campaign_deleted_at" ON "boutique_campaign" ("deleted_at") WHERE deleted_at IS NULL;`);
10
+ }
11
+
12
+ override async down(): Promise<void> {
13
+ this.addSql(`drop table if exists "boutique_campaign" cascade;`);
14
+ }
15
+
16
+ }
@@ -0,0 +1,13 @@
1
+ import { Migration } from "@medusajs/framework/mikro-orm/migrations";
2
+
3
+ export class Migration20260928145243 extends Migration {
4
+
5
+ override async up(): Promise<void> {
6
+ this.addSql(`alter table if exists "boutique_campaign" add column if not exists "logo" text null, add column if not exists "promo_image" text null, add column if not exists "show_countdown" boolean null;`);
7
+ }
8
+
9
+ override async down(): Promise<void> {
10
+ this.addSql(`alter table if exists "boutique_campaign" drop column if exists "logo", drop column if exists "promo_image", drop column if exists "show_countdown";`);
11
+ }
12
+
13
+ }
@@ -0,0 +1,28 @@
1
+ import { model } from "@medusajs/framework/utils"
2
+
3
+ /**
4
+ * Campagne d'une boutique (une par sales channel).
5
+ * `texts` ne contient que les textes modifiés : un champ absent = texte par défaut du thème,
6
+ * complété par le storefront (themes/<id>/campaign.<lang>.json). « Réinitialiser » = vider `texts`.
7
+ */
8
+ const BoutiqueCampaign = model.define("boutique_campaign", {
9
+ id: model.id({ prefix: "bcamp" }).primaryKey(),
10
+ sales_channel_id: model.text().unique(),
11
+ starts_at: model.dateTime(),
12
+ ends_at: model.dateTime(),
13
+ next_campaign: model.text().nullable(),
14
+ /** { topbar?, hero?: { eyebrow, title, highlight, sub, cta, cta2, bubble }, promo?, sections? } */
15
+ texts: model.json().nullable(),
16
+ /** { discount?, freeShipping? } : variables des textes du thème */
17
+ vars: model.json().nullable(),
18
+ /** URL du visuel du hero (module File → stockage S3) */
19
+ hero_image: model.text().nullable(),
20
+ /** Logo de la boutique (aussi utilisé comme favicon) ; vide = pastille de couleur + nom */
21
+ logo: model.text().nullable(),
22
+ /** Image du bandeau promo */
23
+ promo_image: model.text().nullable(),
24
+ /** Compte à rebours : null = selon le thème, true = toujours, false = jamais */
25
+ show_countdown: model.boolean().nullable(),
26
+ })
27
+
28
+ export default BoutiqueCampaign
@@ -0,0 +1,6 @@
1
+ import { MedusaService } from "@medusajs/framework/utils"
2
+ import BoutiqueCampaign from "./models/campaign"
3
+
4
+ class CampaignModuleService extends MedusaService({ BoutiqueCampaign }) {}
5
+
6
+ export default CampaignModuleService
@@ -0,0 +1,94 @@
1
+ import { z } from "@medusajs/framework/zod"
2
+
3
+ /**
4
+ * Validation d'une campagne : même format que config/campaigns/<slug>.json.
5
+ * Texte simple uniquement (aucun < ou >), longueurs bornées, dates cohérentes.
6
+ */
7
+ const plain = (max: number) =>
8
+ z
9
+ .string()
10
+ .trim()
11
+ .max(max)
12
+ .refine((v) => !/[<>]/.test(v), "Texte simple uniquement (pas de HTML)")
13
+
14
+ const slug = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
15
+
16
+ export const CampaignTexts = z.object({
17
+ topbar: z.array(plain(80)).max(5).optional(),
18
+ hero: z
19
+ .object({
20
+ eyebrow: plain(40),
21
+ title: plain(60),
22
+ highlight: plain(40),
23
+ sub: plain(160),
24
+ cta: plain(30),
25
+ cta2: plain(30),
26
+ bubble: z.array(plain(12)).max(3),
27
+ })
28
+ .partial()
29
+ .optional(),
30
+ promo: z
31
+ .object({ eyebrow: plain(40), title: plain(60), text: plain(160), cta: plain(30), seal: plain(20) })
32
+ .partial()
33
+ .optional(),
34
+ sections: z.object({ categories: plain(40), products: plain(40) }).partial().optional(),
35
+ })
36
+
37
+ const imageUrl = z
38
+ .url()
39
+ .max(500)
40
+ .refine((u) => /\.(jpe?g|png|webp)(\?.*)?$/i.test(u), "Image : jpg, png ou webp")
41
+
42
+ // Objet simple (sans .refine) : forme attendue par validateAndTransformBody.
43
+ // La cohérence des dates est vérifiée dans l'étape du workflow (upsert-campaign).
44
+ export const CampaignInput = CampaignTexts.extend({
45
+ startsAt: z.iso.datetime({ offset: true }),
46
+ endsAt: z.iso.datetime({ offset: true }),
47
+ nextCampaign: slug.nullish(),
48
+ vars: z.object({ discount: plain(12), freeShipping: plain(20) }).partial().nullish(),
49
+ heroImage: imageUrl.nullish(),
50
+ logo: imageUrl.nullish(),
51
+ promoImage: imageUrl.nullish(),
52
+ /** null = selon le thème */
53
+ showCountdown: z.boolean().nullish(),
54
+ })
55
+
56
+ export type CampaignInput = z.infer<typeof CampaignInput>
57
+
58
+ /** Format du fichier config/campaigns/<slug>.json (hero.image possible, héritage de l'étape 1). */
59
+ export const CampaignFile = CampaignTexts.extend({
60
+ startsAt: z.string(),
61
+ endsAt: z.string(),
62
+ nextCampaign: slug.optional(),
63
+ vars: z.object({ discount: z.string(), freeShipping: z.string() }).partial().optional(),
64
+ hero: CampaignTexts.shape.hero.unwrap().extend({ image: z.string().optional() }).optional(),
65
+ promo: CampaignTexts.shape.promo.unwrap().extend({ image: z.string().optional() }).optional(),
66
+ logo: z.string().optional(),
67
+ showCountdown: z.boolean().optional(),
68
+ })
69
+
70
+ /** Campagne en base → format JSON du fichier / de la route store. */
71
+ export function toCampaignJson(c: {
72
+ starts_at: Date | string
73
+ ends_at: Date | string
74
+ next_campaign?: string | null
75
+ texts?: Record<string, unknown> | null
76
+ vars?: Record<string, unknown> | null
77
+ hero_image?: string | null
78
+ logo?: string | null
79
+ promo_image?: string | null
80
+ show_countdown?: boolean | null
81
+ }) {
82
+ const texts = (c.texts ?? {}) as z.infer<typeof CampaignTexts>
83
+ return {
84
+ startsAt: new Date(c.starts_at).toISOString(),
85
+ endsAt: new Date(c.ends_at).toISOString(),
86
+ ...(c.next_campaign ? { nextCampaign: c.next_campaign } : {}),
87
+ ...(c.vars ? { vars: c.vars } : {}),
88
+ ...texts,
89
+ ...(c.hero_image ? { hero: { ...(texts.hero ?? {}), image: c.hero_image } } : {}),
90
+ ...(c.promo_image ? { promo: { ...(texts.promo ?? {}), image: c.promo_image } } : {}),
91
+ ...(c.logo ? { logo: c.logo } : {}),
92
+ ...(c.show_countdown != null ? { showCountdown: c.show_countdown } : {}),
93
+ }
94
+ }
@@ -0,0 +1,6 @@
1
+ import { ModuleProvider, Modules } from "@medusajs/framework/utils"
2
+ import ResendNotificationProviderService from "./service"
3
+
4
+ export default ModuleProvider(Modules.NOTIFICATION, {
5
+ services: [ResendNotificationProviderService],
6
+ })
@@ -0,0 +1,63 @@
1
+ import { Logger, NotificationTypes } from "@medusajs/framework/types"
2
+ import { AbstractNotificationProviderService, MedusaError } from "@medusajs/framework/utils"
3
+ import { Resend } from "resend"
4
+
5
+ type Options = {
6
+ api_key: string
7
+ /** Adresse d'envoi vérifiée chez Resend (domaine de la plateforme), ex. orders@mail.notre-domaine.com */
8
+ from: string
9
+ }
10
+
11
+ type InjectedDependencies = { logger: Logger }
12
+
13
+ /**
14
+ * Envoi des e-mails via Resend (une seule clé pour tous les projets, mode plateforme).
15
+ * Le contenu (sujet, HTML, texte) est préparé par src/lib/emails.ts ; ce provider ne fait qu'envoyer.
16
+ * data.fromName = nom de la boutique ; data.replyTo = e-mail du marchand (jamais en « De : »).
17
+ */
18
+ class ResendNotificationProviderService extends AbstractNotificationProviderService {
19
+ static identifier = "notification-resend"
20
+
21
+ private client: Resend
22
+ private options: Options
23
+ private logger: Logger
24
+
25
+ static validateOptions(options: Record<string, unknown>) {
26
+ if (!options.api_key) throw new MedusaError(MedusaError.Types.INVALID_DATA, "Resend : option api_key manquante")
27
+ if (!options.from) throw new MedusaError(MedusaError.Types.INVALID_DATA, "Resend : option from (EMAIL_FROM) manquante")
28
+ }
29
+
30
+ constructor({ logger }: InjectedDependencies, options: Options) {
31
+ super()
32
+ this.client = new Resend(options.api_key)
33
+ this.options = options
34
+ this.logger = logger
35
+ }
36
+
37
+ async send(notification: NotificationTypes.ProviderSendNotificationDTO): Promise<NotificationTypes.ProviderSendNotificationResultsDTO> {
38
+ const { content, data } = notification
39
+ if (!content?.subject || !content.html) {
40
+ throw new MedusaError(MedusaError.Types.INVALID_DATA, `Resend : contenu manquant pour ${notification.template}`)
41
+ }
42
+
43
+ const fromName = typeof data?.fromName === "string" ? data.fromName.replace(/[<>"]/g, "") : undefined
44
+ const replyTo = typeof data?.replyTo === "string" ? data.replyTo : undefined
45
+
46
+ const { data: sent, error } = await this.client.emails.send({
47
+ from: fromName ? `${fromName} <${this.options.from}>` : this.options.from,
48
+ to: [notification.to],
49
+ subject: content.subject,
50
+ html: content.html,
51
+ text: content.text,
52
+ ...(replyTo ? { replyTo } : {}),
53
+ })
54
+
55
+ if (error) {
56
+ this.logger.error(`[resend] ${notification.template} → ${notification.to} : ${error.message}`)
57
+ throw new MedusaError(MedusaError.Types.UNEXPECTED_STATE, error.message)
58
+ }
59
+ return { id: sent?.id }
60
+ }
61
+ }
62
+
63
+ export default ResendNotificationProviderService
@@ -0,0 +1,63 @@
1
+ # Custom CLI Script
2
+
3
+ A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run as a CLI tool.
4
+
5
+ > Learn more about custom CLI scripts in [this documentation](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts).
6
+
7
+ ## How to Create a Custom CLI Script?
8
+
9
+ To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
10
+
11
+ For example, create the file `src/scripts/my-script.ts` with the following content:
12
+
13
+ ```ts title="src/scripts/my-script.ts"
14
+ import {
15
+ ExecArgs,
16
+ } from "@medusajs/framework/types"
17
+
18
+ export default async function myScript ({
19
+ container
20
+ }: ExecArgs) {
21
+ const productModuleService = container.resolve("product")
22
+
23
+ const [, count] = await productModuleService.listAndCountProducts()
24
+
25
+ console.log(`You have ${count} product(s)`)
26
+ }
27
+ ```
28
+
29
+ The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
30
+
31
+ ---
32
+
33
+ ## How to Run Custom CLI Script?
34
+
35
+ To run the custom CLI script, run the `exec` command:
36
+
37
+ ```bash
38
+ npx medusa exec ./src/scripts/my-script.ts
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Custom CLI Script Arguments
44
+
45
+ Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
46
+
47
+ For example:
48
+
49
+ ```ts
50
+ import { ExecArgs } from "@medusajs/framework/types"
51
+
52
+ export default async function myScript ({
53
+ args
54
+ }: ExecArgs) {
55
+ console.log(`The arguments you passed: ${args}`)
56
+ }
57
+ ```
58
+
59
+ Then, pass the arguments in the `exec` command after the file path:
60
+
61
+ ```bash
62
+ npx medusa exec ./src/scripts/my-script.ts arg1 arg2
63
+ ```
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `create-boutique campaign export` : campagnes en base → JSON (un objet par slug de boutique),
3
+ * imprimé entre deux marqueurs pour que le CLI l'écrive dans config/campaigns/<slug>.json.
4
+ */
5
+ import { ExecArgs } from "@medusajs/framework/types"
6
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
7
+ import { toCampaignJson } from "../modules/campaign/validators"
8
+
9
+ export default async function campaignExport({ container }: ExecArgs) {
10
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
11
+ const { data } = await query.graph({ entity: "sales_channel", fields: ["name", "boutique_campaign.*"] })
12
+ const out: Record<string, unknown> = {}
13
+ for (const c of data) if (c.boutique_campaign) out[c.name] = toCampaignJson(c.boutique_campaign)
14
+ process.stdout.write(`<<<CAMPAIGNS${JSON.stringify(out)}CAMPAIGNS>>>\n`)
15
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `create-boutique campaign import` : JSON (CAMPAIGN_IMPORT, base64, { slug: campagne }) → base.
3
+ * ÉCRASE la campagne en base (le CLI demande confirmation). Mêmes validations que l'admin.
4
+ */
5
+ import { ExecArgs } from "@medusajs/framework/types"
6
+ import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
7
+ import { CampaignFile, CampaignInput } from "../modules/campaign/validators"
8
+ import { upsertCampaignWorkflow } from "../workflows/upsert-campaign"
9
+
10
+ export default async function campaignImport({ container }: ExecArgs) {
11
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
12
+ const raw = process.env.CAMPAIGN_IMPORT
13
+ if (!raw) throw new Error("CAMPAIGN_IMPORT absent")
14
+ const data: Record<string, unknown> = JSON.parse(Buffer.from(raw, "base64").toString("utf8"))
15
+
16
+ const salesChannels = container.resolve(Modules.SALES_CHANNEL)
17
+ let count = 0
18
+ for (const [slug, value] of Object.entries(data)) {
19
+ const [channel] = await salesChannels.listSalesChannels({ name: slug })
20
+ if (!channel) throw new Error(`boutique inconnue en base : ${slug}`)
21
+ const file = CampaignFile.parse(value)
22
+ const { image: heroImage, ...hero } = file.hero ?? {}
23
+ const { image: promoImage, ...promo } = file.promo ?? {}
24
+ const campaign = CampaignInput.parse({
25
+ ...file,
26
+ hero: file.hero ? hero : undefined,
27
+ promo: file.promo ? promo : undefined,
28
+ // Seules les URL sont reprises (un nom de fichier n'est pas une image stockée).
29
+ heroImage: heroImage && /^https?:\/\//.test(heroImage) ? heroImage : undefined,
30
+ promoImage: promoImage && /^https?:\/\//.test(promoImage) ? promoImage : undefined,
31
+ logo: file.logo && /^https?:\/\//.test(file.logo) ? file.logo : undefined,
32
+ })
33
+ await upsertCampaignWorkflow(container).run({ input: { sales_channel_id: channel.id, campaign } })
34
+ count++
35
+ }
36
+ logger.info(`${count} campagne(s) importée(s)`)
37
+ process.stdout.write(`${count} campagne(s) importée(s)\n`)
38
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `create-boutique apply` / `check` : produits publiés sans prix dans la devise de chaque boutique
3
+ * (invisibles dans cette boutique). Résultat JSON entre marqueurs, lu par le CLI.
4
+ */
5
+ import { ExecArgs } from "@medusajs/framework/types"
6
+ import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
7
+ import { readProject } from "../lib/project"
8
+
9
+ export default async function priceReport({ container }: ExecArgs) {
10
+ const query = container.resolve(ContainerRegistrationKeys.QUERY)
11
+ const project = readProject()
12
+ const report: Record<string, { currency: string; missing: string[]; published: number }> = {}
13
+
14
+ for (const b of project.boutiques) {
15
+ const { data: channels } = await query.graph({
16
+ entity: "sales_channel",
17
+ fields: ["products.handle", "products.status", "products.variants.prices.currency_code"],
18
+ filters: { name: b.slug },
19
+ })
20
+ // Produits liés au canal (lien product ↔ sales_channel, absent du type généré)
21
+ const products = ((channels[0] as unknown as { products?: unknown[] })?.products ?? []) as {
22
+ handle: string
23
+ status: string
24
+ variants?: { prices?: { currency_code: string }[] }[]
25
+ }[]
26
+ const missing = products
27
+ .filter((p) => p && p.status === "published")
28
+ .filter((p) => !(p.variants ?? []).some((v) => (v.prices ?? []).some((pr) => pr.currency_code === b.currency)))
29
+ .map((p) => p.handle)
30
+ report[b.slug] = { currency: b.currency, missing, published: products.filter((p) => p && p.status === "published").length }
31
+ }
32
+
33
+ process.stdout.write(`<<<PRICES${JSON.stringify(report)}PRICES>>>\n`)
34
+ }