@ecomiq/storefront 2.1.4 → 3.0.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.
- package/LICENSE +21 -0
- package/README.md +377 -76
- package/dist/account-Dx4e3syi.js +258 -0
- package/dist/account-forms-AVk71ko7.js +39 -0
- package/dist/account-forms.d.ts +13 -0
- package/dist/account.d.ts +240 -0
- package/dist/address-defaults-BqjRhS82.js +14 -0
- package/dist/address-defaults.d.ts +11 -0
- package/dist/analytics/analytics-CG1Qagwl.js +148 -0
- package/dist/analytics/analytics.d.ts +1 -0
- package/dist/analytics/config-DJr-GRhs.js +62 -0
- package/dist/analytics/config.d.ts +22 -0
- package/dist/analytics/consent-B13rBpBs.js +57 -0
- package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
- package/dist/analytics/consent-banner.d.ts +20 -0
- package/dist/analytics/cookies-pLozllyB.js +100 -0
- package/dist/analytics/cookies.d.ts +6 -0
- package/dist/analytics/destinations-iECrlLIr.js +144 -0
- package/dist/analytics/events-Dt81tIp1.js +102 -0
- package/dist/analytics/events.d.ts +51 -0
- package/dist/analytics/tracker-p4yaoOyk.js +51 -0
- package/dist/api-cache-DsuIn5hX.js +159 -0
- package/dist/bank-accounts-DlEczQQ5.js +81 -0
- package/dist/bank-accounts.d.ts +4 -0
- package/dist/captcha-DAqaIx8m.js +117 -0
- package/dist/captcha.d.ts +34 -0
- package/dist/cart-D7DxZ2lD.js +30 -0
- package/dist/cart-actions-Bsub_FFG.js +116 -0
- package/dist/cart-actions.d.ts +15 -0
- package/dist/cart-drawer-B7PPsR69.js +22 -0
- package/dist/cart-drawer.d.ts +9 -0
- package/dist/cart-form-y9P1r_EX.js +81 -0
- package/dist/cart-form.d.ts +24 -0
- package/dist/cart-summary-ur2VWKr-.js +89 -0
- package/dist/cart-summary.d.ts +31 -0
- package/dist/cart.d.ts +4 -0
- package/dist/catalog-7gJ-l5hW.js +67 -0
- package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
- package/dist/catalog-hooks.d.ts +111 -0
- package/dist/catalog.d.ts +5 -0
- package/dist/checkout-DVWSXANi.js +474 -0
- package/dist/checkout-form-CbOIdENC.js +131 -0
- package/dist/checkout-form.d.ts +20 -0
- package/dist/checkout.d.ts +86 -0
- package/dist/complaint-book-BvwLvfx0.js +117 -0
- package/dist/complaint-book.d.ts +62 -0
- package/dist/culqi-authentication-BGr20vAJ.js +204 -0
- package/dist/culqi-authentication.d.ts +15 -0
- package/dist/culqi-checkout-CTXpKgp_.js +55 -0
- package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
- package/dist/culqi-three-ds.d.ts +29 -0
- package/dist/customer-avatar-CJXSDroK.js +12 -0
- package/dist/customer-avatar.d.ts +5 -0
- package/dist/customer-cookies-DHOgFAMV.js +45 -0
- package/dist/customer-csrf-C1oDX-Py.js +17 -0
- package/dist/customer-csrf.d.ts +3 -0
- package/dist/customer-document-fields-C1K-_DAD.js +70 -0
- package/dist/customer-document-fields.d.ts +9 -0
- package/dist/customer-login-form-C7AC6vXd.js +144 -0
- package/dist/customer-login-form.d.ts +17 -0
- package/dist/customer-registration-form-BEm_NEfP.js +93 -0
- package/dist/customer-registration-form.d.ts +8 -0
- package/dist/document-url-CGsAl5Tf.js +9 -0
- package/dist/documents-T1mzc_j8.js +38 -0
- package/dist/documents.d.ts +16 -0
- package/dist/entries/account.d.ts +11 -0
- package/dist/entries/account.js +10 -0
- package/dist/entries/analytics.d.ts +6 -0
- package/dist/entries/analytics.js +5 -0
- package/dist/entries/cart.d.ts +8 -0
- package/dist/entries/cart.js +7 -0
- package/dist/entries/checkout.d.ts +12 -0
- package/dist/entries/checkout.js +11 -0
- package/dist/entries/order.d.ts +11 -0
- package/dist/entries/order.js +11 -0
- package/dist/favicon-YpyhVJ9H.js +28 -0
- package/dist/favicon.d.ts +4 -0
- package/dist/form-BtQcbXlJ.js +18 -0
- package/dist/form-contracts-BNpHaTGJ.js +166 -0
- package/dist/form-contracts.d.ts +42 -0
- package/dist/form.d.ts +3 -0
- package/dist/hidden-CCWtOWKR.js +3 -0
- package/dist/hidden.d.ts +4 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +50 -0
- package/dist/invoice-fields-BG0HjL6T.js +103 -0
- package/dist/invoice-fields.d.ts +39 -0
- package/dist/links-kFxBZfdb.js +96 -0
- package/dist/links.d.ts +21 -0
- package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
- package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
- package/dist/mercadopago-dialog.d.ts +21 -0
- package/dist/money-DtGOtDri.js +32 -0
- package/dist/money.d.ts +1 -0
- package/dist/optimistic-cart-BveVOHuh.js +53 -0
- package/dist/optimistic-cart.d.ts +16 -0
- package/dist/order-display-DTt5WBbc.js +112 -0
- package/dist/order-display.d.ts +20 -0
- package/dist/order-draft-DxV1Uhm3.js +38 -0
- package/dist/order-draft.d.ts +16 -0
- package/dist/order-pages-Dm6l5m0m.js +38 -0
- package/dist/order-pages.d.ts +12 -0
- package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
- package/dist/order-payment-continuation.d.ts +10 -0
- package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
- package/dist/order-payment-in-review.d.ts +5 -0
- package/dist/order-status-BJXLJwxq.js +162 -0
- package/dist/order-status.d.ts +89 -0
- package/dist/order-view-9IlBg5AF.js +62 -0
- package/dist/order-view.d.ts +12 -0
- package/dist/page-action-Ctkf4xC6.js +7 -0
- package/dist/page-seo-a5FJl8x5.js +262 -0
- package/dist/page-seo.d.ts +34 -0
- package/dist/pages-Ca_Gt6mq.js +150 -0
- package/dist/pages.d.ts +45 -0
- package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
- package/dist/payment-logo-BoPaGJc_.js +46 -0
- package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
- package/dist/payment-logo.d.ts +11 -0
- package/dist/payment-logos-BYvdwYyJ.js +23 -0
- package/dist/payment-methods-B8ocTgFZ.js +73 -0
- package/dist/payment-methods.d.ts +58 -0
- package/dist/payment-return-5cjypwIw.js +7 -0
- package/dist/place-CmW4hBWY.js +13 -0
- package/dist/place.d.ts +4 -0
- package/dist/preview-BiIBif3C.js +427 -0
- package/dist/preview-address-Cf0sJ-9p.js +21 -0
- package/dist/preview-cart-Dg2CVktS.js +376 -0
- package/dist/product-preview-BspMAecJ.js +51 -0
- package/dist/product-preview.d.ts +5 -0
- package/dist/product-variants-BNDHvs1y.js +46 -0
- package/dist/product-variants.d.ts +12 -0
- package/dist/public-pages-XtwlaLwt.js +194 -0
- package/dist/public-pages.d.ts +67 -0
- package/dist/registration-CpDcv8ql.js +91 -0
- package/dist/registration.d.ts +12 -0
- package/dist/request-security-CRsRMgwj.js +76 -0
- package/dist/rich-text-M-Ju8RL4.js +9 -0
- package/dist/rich-text.d.ts +5 -0
- package/dist/route-text-DcJw2SsK.js +9 -0
- package/dist/route-text.d.ts +4 -0
- package/dist/section-preview-CmsMl3yu.js +83 -0
- package/dist/section-preview.d.ts +12 -0
- package/dist/sections/account-addresses.d.ts +18 -0
- package/dist/sections/account-addresses.js +375 -0
- package/dist/sections/account-order.d.ts +12 -0
- package/dist/sections/account-order.js +592 -0
- package/dist/sections/account-orders.d.ts +8 -0
- package/dist/sections/account-orders.js +359 -0
- package/dist/sections/account-profile.d.ts +16 -0
- package/dist/sections/account-profile.js +123 -0
- package/dist/sections/checkout.d.ts +24 -0
- package/dist/sections/checkout.js +963 -0
- package/dist/sections/complaint-book.d.ts +8 -0
- package/dist/sections/complaint-book.js +538 -0
- package/dist/sections/design-system.d.ts +1 -0
- package/dist/sections/design-system.js +310 -0
- package/dist/sections/draft-invoice.d.ts +20 -0
- package/dist/sections/draft-invoice.js +158 -0
- package/dist/sections/order-tracking.d.ts +19 -0
- package/dist/sections/order-tracking.js +141 -0
- package/dist/sections-CNUMWTF_.js +77 -0
- package/dist/sections.d.ts +19 -0
- package/dist/seo-GpDRhAAA.js +78 -0
- package/dist/session-CKvmFJyg.js +379 -0
- package/dist/session.d.ts +24 -0
- package/dist/settings-B0NO2A6Y.js +78 -0
- package/dist/settings.d.ts +51 -0
- package/dist/source-marker-DhqSkW7n.js +4 -0
- package/dist/sources-B-3JFJfb.js +46 -0
- package/dist/sources.d.ts +7 -0
- package/dist/store-8Q-_LFrQ.js +152 -0
- package/dist/store-contact--hY6CoFb.js +19 -0
- package/dist/store-contact.d.ts +6 -0
- package/dist/store.d.ts +17 -0
- package/dist/storefront-language-DrMjVbmk.js +29 -0
- package/dist/storefront-language.d.ts +14 -0
- package/dist/storefront-text-D25ae1qf.js +11 -0
- package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
- package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
- package/dist/storefront-text.d.ts +4 -0
- package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
- package/dist/stripe-dialog.d.ts +30 -0
- package/dist/telemetry-HuZXWBPi.js +114 -0
- package/dist/telemetry.d.ts +10 -0
- package/dist/templates-CAqoNyTW.js +40 -0
- package/dist/templates.d.ts +15 -0
- package/dist/urls-CpGQPxvf.js +71 -0
- package/dist/urls.d.ts +18 -0
- package/dist/use-checkout-CZcxXJXi.js +42 -0
- package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
- package/dist/use-checkout-payment.d.ts +18 -0
- package/dist/use-checkout.d.ts +36 -0
- package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
- package/dist/use-order-payment-BKIz_IO0.js +45 -0
- package/dist/use-order-payment.d.ts +18 -0
- package/dist/use-payment-token-CErx-tN-.js +105 -0
- package/dist/vite-payment-logos-kziu7wKb.js +27 -0
- package/dist/vite-payment-logos.d.ts +24 -0
- package/dist/vite-public-assets-DW2fgtVb.js +209 -0
- package/dist/vite-public-assets.d.ts +29 -0
- package/dist/vite-sections-DTBr0-Kg.js +464 -0
- package/dist/vite-sections.d.ts +21 -0
- package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
- package/dist/vite-theme-ui.d.ts +10 -0
- package/dist/vite.d.ts +36 -0
- package/dist/vite.js +127 -0
- package/dist/voucher-payment-CVFDZ1L8.js +66 -0
- package/dist/worker-CNi-gzCu.js +86 -0
- package/dist/worker.d.ts +27 -0
- package/package.json +100 -76
- package/src/sections/account-addresses.tsx +215 -0
- package/src/sections/account-order.tsx +424 -0
- package/src/sections/account-orders.tsx +259 -0
- package/src/sections/account-profile.tsx +70 -0
- package/src/sections/checkout.tsx +586 -0
- package/src/sections/complaint-book.tsx +325 -0
- package/src/sections/design-system.tsx +146 -0
- package/src/sections/draft-invoice.tsx +105 -0
- package/src/sections/order-tracking.tsx +81 -0
- package/src/vite-client.d.ts +4 -0
- package/bin/ecomiq.mjs +0 -179
- package/public/design-bridge.js +0 -318
- package/src/api-proxy.ts +0 -194
- package/src/auth/create-storefront-auth.ts +0 -94
- package/src/auth/ecomiq-oauth-password-client.ts +0 -26
- package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
- package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
- package/src/auth/ecomiq-oauth-password-types.ts +0 -24
- package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
- package/src/auth/env.ts +0 -7
- package/src/auth/secure-cookies.ts +0 -23
- package/src/auth/storefront-auth-base-url.ts +0 -117
- package/src/auth/storefront-auth.ts +0 -7
- package/src/cart-session.ts +0 -239
- package/src/client.ts +0 -48
- package/src/codegen.mjs +0 -801
- package/src/components/account/customer-addresses-panel.tsx +0 -678
- package/src/components/account/customer-order-detail-panel.tsx +0 -285
- package/src/components/account/customer-orders-panel.tsx +0 -192
- package/src/components/account/customer-profile-panel.tsx +0 -379
- package/src/components/checkout/cart-sync.ts +0 -143
- package/src/components/checkout/checkout-contact-section.tsx +0 -76
- package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
- package/src/components/checkout/checkout-form-controls.tsx +0 -139
- package/src/components/checkout/checkout-line-items.tsx +0 -43
- package/src/components/checkout/checkout-order-summary.tsx +0 -165
- package/src/components/checkout/checkout-payment-section.tsx +0 -212
- package/src/components/checkout/checkout-session-view.tsx +0 -564
- package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
- package/src/components/checkout/checkout-shipping-section.tsx +0 -98
- package/src/components/checkout/checkout-states.tsx +0 -73
- package/src/components/checkout/checkout-step-card.tsx +0 -101
- package/src/components/checkout/checkout-steps.ts +0 -111
- package/src/components/checkout/form-schema.ts +0 -281
- package/src/components/checkout/format.ts +0 -15
- package/src/components/checkout/index.ts +0 -5
- package/src/components/checkout/map-customer-to-checkout.ts +0 -188
- package/src/components/checkout/types.ts +0 -41
- package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
- package/src/components/checkout/use-checkout-geo.ts +0 -50
- package/src/components/checkout/use-checkout-session.ts +0 -640
- package/src/components/hero.tsx +0 -291
- package/src/config.d.ts +0 -94
- package/src/config.mjs +0 -146
- package/src/format-money.ts +0 -31
- package/src/health.ts +0 -90
- package/src/hooks/index.ts +0 -9
- package/src/hooks/recently-viewed.ts +0 -125
- package/src/index.ts +0 -349
- package/src/link.tsx +0 -2
- package/src/pages/account/account-page-skeleton.tsx +0 -69
- package/src/pages/account/account-page-view.tsx +0 -270
- package/src/pages/account/account-shell.tsx +0 -101
- package/src/pages/account/parse-account-route.ts +0 -36
- package/src/pages/account.tsx +0 -16
- package/src/pages/api.$.ts +0 -20
- package/src/pages/api.auth.$.ts +0 -16
- package/src/pages/checkout.$checkoutId.tsx +0 -60
- package/src/pages/checkout.tsx +0 -31
- package/src/pages/pages.$pageSlug.tsx +0 -74
- package/src/pages/thank-you.tsx +0 -53
- package/src/providers/auth.tsx +0 -96
- package/src/providers/cart.tsx +0 -275
- package/src/providers/ecomiq.ts +0 -137
- package/src/providers/index.ts +0 -26
- package/src/proxy-api.ts +0 -20
- package/src/public-config.ts +0 -24
- package/src/review-submission.ts +0 -89
- package/src/runtime/context.ts +0 -2
- package/src/runtime/default-layout.tsx +0 -64
- package/src/runtime/head.ts +0 -149
- package/src/runtime/page.tsx +0 -143
- package/src/runtime/postcss-browser-stub.mjs +0 -5
- package/src/server-config-store.ts +0 -35
- package/src/server.ts +0 -286
- package/src/styles.css +0 -76
- package/src/ui/badge.tsx +0 -32
- package/src/ui/button.tsx +0 -31
- package/src/ui/card.tsx +0 -31
- package/src/ui/carousel.tsx +0 -218
- package/src/ui/cn.ts +0 -6
- package/src/ui/index.ts +0 -8
- package/src/ui/separator.tsx +0 -6
- package/src/ui/skeleton.tsx +0 -6
- package/src/ui/storefront-error.tsx +0 -49
- package/src/utils.ts +0 -36
- package/src/vite-inline.mjs +0 -263
- package/templates/__root.tsx +0 -83
- package/templates/router.tsx +0 -19
package/src/codegen.mjs
DELETED
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Codegen de Ecomiq.
|
|
3
|
-
*
|
|
4
|
-
* Lee `pages/` y `ecomiq.config.js` del proyecto del usuario y genera una app
|
|
5
|
-
* interna en `.ecomiq/` que sigue las convenciones de TanStack Start
|
|
6
|
-
* (routes con createFileRoute, router.tsx, vite.config.ts). El usuario nunca
|
|
7
|
-
* ve ni edita estos archivos: son un artefacto de build, como `.next/`.
|
|
8
|
-
*
|
|
9
|
-
* Convención de páginas:
|
|
10
|
-
* pages/index.tsx → /
|
|
11
|
-
* pages/products.$productId.tsx → /products/:productId
|
|
12
|
-
* pages/products.index.tsx → /products
|
|
13
|
-
* pages/api/$.ts → /api/$ (server route / proxy)
|
|
14
|
-
*
|
|
15
|
-
* Páginas cerradas del framework (siempre generadas, no overrideables):
|
|
16
|
-
* /checkout/ ← redirect a /cart (checkout.index, sin layout padre)
|
|
17
|
-
* /checkout/$checkoutId ← flujo de checkout
|
|
18
|
-
* /thank-you ← post-compra
|
|
19
|
-
* /account/* ← área de cuenta (login/register en template: /login, /register)
|
|
20
|
-
* /api/$ ← proxy BFF same-origin
|
|
21
|
-
* /api/auth/$ ← Better Auth (handlers del framework)
|
|
22
|
-
*/
|
|
23
|
-
import {
|
|
24
|
-
mkdirSync,
|
|
25
|
-
readdirSync,
|
|
26
|
-
readFileSync,
|
|
27
|
-
rmSync,
|
|
28
|
-
watch as watchFileSystem,
|
|
29
|
-
writeFileSync,
|
|
30
|
-
existsSync,
|
|
31
|
-
symlinkSync,
|
|
32
|
-
lstatSync,
|
|
33
|
-
readlinkSync,
|
|
34
|
-
unlinkSync,
|
|
35
|
-
statSync,
|
|
36
|
-
realpathSync,
|
|
37
|
-
} from 'node:fs'
|
|
38
|
-
import { createRequire } from 'node:module'
|
|
39
|
-
import { resolve, join, relative, dirname, isAbsolute, sep } from 'node:path'
|
|
40
|
-
|
|
41
|
-
const HEADER = '/* Generado por Ecomiq — no editar. */\n'
|
|
42
|
-
const ROUTE_MANIFEST_FILE = 'generated-routes.json'
|
|
43
|
-
|
|
44
|
-
/** Rutas reservadas que viven solo en el package (templates no pueden overridear). */
|
|
45
|
-
const FRAMEWORK_LOCKED_PAGES = [
|
|
46
|
-
{
|
|
47
|
-
// index (no `checkout.tsx`): evita que TanStack anide $checkoutId bajo un layout
|
|
48
|
-
// que redirige a /cart.
|
|
49
|
-
rel: 'checkout.index.tsx',
|
|
50
|
-
routeId: '/checkout/',
|
|
51
|
-
importFrom: '@ecomiq/storefront/internal/checkout',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
rel: 'checkout.$checkoutId.tsx',
|
|
55
|
-
routeId: '/checkout/$checkoutId',
|
|
56
|
-
importFrom: '@ecomiq/storefront/internal/checkout-session',
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
rel: 'thank-you.tsx',
|
|
60
|
-
routeId: '/thank-you',
|
|
61
|
-
importFrom: '@ecomiq/storefront/internal/thank-you',
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
rel: 'pages.$pageSlug.tsx',
|
|
65
|
-
routeId: '/pages/$pageSlug',
|
|
66
|
-
importFrom: '@ecomiq/storefront/internal/cms-page',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
rel: 'account.index.tsx',
|
|
70
|
-
routeId: '/account/',
|
|
71
|
-
importFrom: '@ecomiq/storefront/internal/account',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
rel: 'account.addresses.tsx',
|
|
75
|
-
routeId: '/account/addresses',
|
|
76
|
-
importFrom: '@ecomiq/storefront/internal/account',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
rel: 'account.orders.tsx',
|
|
80
|
-
routeId: '/account/orders',
|
|
81
|
-
importFrom: '@ecomiq/storefront/internal/account',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
rel: 'account.orders.$orderId.tsx',
|
|
85
|
-
routeId: '/account/orders/$orderId',
|
|
86
|
-
importFrom: '@ecomiq/storefront/internal/account',
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
rel: 'account.devices.tsx',
|
|
90
|
-
routeId: '/account/devices',
|
|
91
|
-
importFrom: '@ecomiq/storefront/internal/account',
|
|
92
|
-
},
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
/** Server routes cerradas del framework (proxy BFF, Better Auth, etc.). */
|
|
96
|
-
const FRAMEWORK_LOCKED_API_ROUTES = [
|
|
97
|
-
{
|
|
98
|
-
rel: 'api/$.ts',
|
|
99
|
-
routeId: '/api/$',
|
|
100
|
-
importFrom: '@ecomiq/storefront/internal/api-proxy',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
rel: 'api/auth.$.ts',
|
|
104
|
-
routeId: '/api/auth/$',
|
|
105
|
-
importFrom: '@ecomiq/storefront/internal/auth-api',
|
|
106
|
-
},
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
110
|
-
const pagesDir = resolve(cwd, 'pages')
|
|
111
|
-
if (!existsSync(pagesDir)) {
|
|
112
|
-
throw new Error('No existe la carpeta pages/. Crea al menos pages/index.tsx')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const srcDir = join(appDir, 'src')
|
|
116
|
-
const routesDir = join(srcDir, 'routes')
|
|
117
|
-
mkdirSync(routesDir, { recursive: true })
|
|
118
|
-
|
|
119
|
-
// ── 1. dependencias internas resolubles desde .ecomiq (soporta npm, pnpm y monorepos) ──
|
|
120
|
-
linkFrameworkDependencies(appDir, frameworkRoot, cwd)
|
|
121
|
-
|
|
122
|
-
// ── 2. package.json interno (type: module para que Vite resuelva bien) ──
|
|
123
|
-
writeGeneratedFile(
|
|
124
|
-
join(appDir, 'package.json'),
|
|
125
|
-
JSON.stringify({ name: 'ecomiq-app', private: true, type: 'module' }, null, 2),
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
// ── 3. tsconfig interno ──
|
|
129
|
-
writeGeneratedFile(
|
|
130
|
-
join(appDir, 'tsconfig.json'),
|
|
131
|
-
JSON.stringify(
|
|
132
|
-
{
|
|
133
|
-
compilerOptions: {
|
|
134
|
-
jsx: 'react-jsx',
|
|
135
|
-
module: 'ESNext',
|
|
136
|
-
moduleResolution: 'Bundler',
|
|
137
|
-
target: 'ES2022',
|
|
138
|
-
strict: true,
|
|
139
|
-
skipLibCheck: true,
|
|
140
|
-
verbatimModuleSyntax: true,
|
|
141
|
-
types: ['vite/client'],
|
|
142
|
-
},
|
|
143
|
-
include: ['src', '../pages', '../lib'],
|
|
144
|
-
},
|
|
145
|
-
null,
|
|
146
|
-
2,
|
|
147
|
-
),
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
const publicConfig = {
|
|
151
|
-
domain: config.domain,
|
|
152
|
-
}
|
|
153
|
-
if (config.theme !== undefined) publicConfig.theme = config.theme
|
|
154
|
-
|
|
155
|
-
for (const serverOnlyKey of ['api', 'cloud', 'dev', 'deploy', 'env']) {
|
|
156
|
-
if (Object.hasOwn(publicConfig, serverOnlyKey)) {
|
|
157
|
-
throw new Error(`La clave server-only \`${serverOnlyKey}\` no puede serializarse en app-config.ts`)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
writeGeneratedFile(
|
|
162
|
-
join(srcDir, 'app-config.ts'),
|
|
163
|
-
HEADER +
|
|
164
|
-
`import type { PublicEcomiqConfig } from '@ecomiq/storefront'\n\n` +
|
|
165
|
-
`const config = ${JSON.stringify(publicConfig, null, 2)} satisfies PublicEcomiqConfig\n` +
|
|
166
|
-
`export default config\n`,
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
writeGeneratedFile(
|
|
170
|
-
join(srcDir, 'server.ts'),
|
|
171
|
-
HEADER +
|
|
172
|
-
`import startServer, { createServerEntry } from '@tanstack/react-start/server-entry'\n` +
|
|
173
|
-
`import config from '../../ecomiq.config.js'\n` +
|
|
174
|
-
`import { registerServerConfig } from '@ecomiq/storefront/internal/server-config'\n` +
|
|
175
|
-
`import { handleEcomiqHealth } from '@ecomiq/storefront/internal/health'\n\n` +
|
|
176
|
-
`export default createServerEntry({\n` +
|
|
177
|
-
` async fetch(request, ...rest) {\n` +
|
|
178
|
-
` let api = config.api\n` +
|
|
179
|
-
` if (import.meta.env.DEV && config.dev?.apiBaseUrl) {\n` +
|
|
180
|
-
` api = { ...api, baseUrl: config.dev.apiBaseUrl }\n` +
|
|
181
|
-
` }\n` +
|
|
182
|
-
` let cloud = config.cloud\n` +
|
|
183
|
-
` if (import.meta.env.DEV && !cloud?.baseUrl) {\n` +
|
|
184
|
-
` cloud = {\n` +
|
|
185
|
-
` timeoutMs: cloud?.timeoutMs ?? 3_000,\n` +
|
|
186
|
-
` cacheTtlMs: cloud?.cacheTtlMs ?? 15_000,\n` +
|
|
187
|
-
` ...cloud,\n` +
|
|
188
|
-
` baseUrl: 'http://127.0.0.1:8020',\n` +
|
|
189
|
-
` }\n` +
|
|
190
|
-
` }\n` +
|
|
191
|
-
` registerServerConfig({ domain: config.domain, api, cloud })\n` +
|
|
192
|
-
` const health = await handleEcomiqHealth(request)\n` +
|
|
193
|
-
` if (health) return health\n` +
|
|
194
|
-
` return startServer.fetch(request, ...rest)\n` +
|
|
195
|
-
` },\n` +
|
|
196
|
-
`})\n`,
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
// ── 5. styles.css: tokens del framework + fuentes de contenido del usuario ──
|
|
200
|
-
const themeOverrides = buildThemeOverrides(config)
|
|
201
|
-
const projectPkgPath = resolve(cwd, 'package.json')
|
|
202
|
-
const projectPkg = existsSync(projectPkgPath)
|
|
203
|
-
? JSON.parse(readFileSync(projectPkgPath, 'utf8'))
|
|
204
|
-
: {}
|
|
205
|
-
const hasStorefrontDefinition = Boolean(
|
|
206
|
-
projectPkg.dependencies?.['@ecomiq/storefront-definition'] ||
|
|
207
|
-
projectPkg.devDependencies?.['@ecomiq/storefront-definition'],
|
|
208
|
-
)
|
|
209
|
-
writeGeneratedFile(
|
|
210
|
-
join(srcDir, 'styles.css'),
|
|
211
|
-
HEADER.replace('/*', '/*!') +
|
|
212
|
-
`@import '@ecomiq/storefront/styles.css';\n` +
|
|
213
|
-
(hasStorefrontDefinition
|
|
214
|
-
? `@import '@ecomiq/storefront-definition/react/styles.css';\n` +
|
|
215
|
-
`@source '../../../node_modules/@ecomiq/storefront-definition/src/react';\n`
|
|
216
|
-
: '') +
|
|
217
|
-
`@source '../../pages';\n` +
|
|
218
|
-
`@source '../../components';\n` +
|
|
219
|
-
`@source '../../lib';\n` +
|
|
220
|
-
`@source '../../layout.tsx';\n` +
|
|
221
|
-
(themeOverrides ? `\n${themeOverrides}\n` : ''),
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
// ── 6. router.tsx ──
|
|
225
|
-
writeTemplate(frameworkRoot, srcDir, 'router.tsx')
|
|
226
|
-
|
|
227
|
-
// ── 7. __root.tsx (shell HTML + providers) ──
|
|
228
|
-
writeTemplate(frameworkRoot, routesDir, '__root.tsx')
|
|
229
|
-
|
|
230
|
-
// ── 7b. user-layout.tsx: layout.tsx del usuario si existe, si no el default ──
|
|
231
|
-
const hasCustomLayout = existsSync(resolve(cwd, 'layout.tsx')) || existsSync(resolve(cwd, 'layout.jsx'))
|
|
232
|
-
writeGeneratedFile(
|
|
233
|
-
join(srcDir, 'user-layout.tsx'),
|
|
234
|
-
HEADER +
|
|
235
|
-
(hasCustomLayout
|
|
236
|
-
? `export { default } from '../../layout'\n`
|
|
237
|
-
: `export { default } from '@ecomiq/storefront/internal/default-layout'\n`),
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
// ── 8. rutas generadas desde pages/ (páginas UI + API server routes) ──
|
|
241
|
-
const entries = listPageEntries(pagesDir)
|
|
242
|
-
const pageEntries = entries.filter((e) => e.kind === 'page')
|
|
243
|
-
const apiEntries = entries.filter((e) => e.kind === 'api')
|
|
244
|
-
if (pageEntries.length === 0) {
|
|
245
|
-
throw new Error('pages/ está vacío. Crea al menos pages/index.tsx')
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
assertNoFrameworkPageOverrides(pageEntries)
|
|
249
|
-
assertNoFrameworkApiOverrides(apiEntries)
|
|
250
|
-
|
|
251
|
-
const desiredRoutes = [
|
|
252
|
-
...pageEntries.map((entry) => entry.rel.replace(/\.jsx$/, '.tsx')),
|
|
253
|
-
...apiEntries.map((entry) => entry.rel),
|
|
254
|
-
...FRAMEWORK_LOCKED_PAGES.map((page) => page.rel),
|
|
255
|
-
...FRAMEWORK_LOCKED_API_ROUTES.map((route) => route.rel),
|
|
256
|
-
].sort()
|
|
257
|
-
|
|
258
|
-
for (const entry of pageEntries) {
|
|
259
|
-
const routeId = fileToRouteId(entry.rel)
|
|
260
|
-
const outFile = join(routesDir, entry.rel.replace(/\.jsx$/, '.tsx'))
|
|
261
|
-
mkdirSync(dirname(outFile), { recursive: true })
|
|
262
|
-
const importPath = relative(dirname(outFile), join(pagesDir, entry.rel))
|
|
263
|
-
.replace(/\\/g, '/')
|
|
264
|
-
.replace(/\.(tsx|jsx)$/, '')
|
|
265
|
-
writeGeneratedFile(outFile, routeFileSource(routeId, importPath, entry.rel))
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
for (const entry of apiEntries) {
|
|
269
|
-
const routeId = fileToRouteId(entry.rel)
|
|
270
|
-
const outFile = join(routesDir, entry.rel)
|
|
271
|
-
mkdirSync(dirname(outFile), { recursive: true })
|
|
272
|
-
// Extensión explícita: archivos como `$.ts` no resuelven bien sin ella.
|
|
273
|
-
const importPath = relative(dirname(outFile), join(pagesDir, entry.rel)).replace(/\\/g, '/')
|
|
274
|
-
writeGeneratedFile(outFile, apiRouteFileSource(routeId, importPath))
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
for (const locked of FRAMEWORK_LOCKED_PAGES) {
|
|
278
|
-
const outFile = join(routesDir, locked.rel)
|
|
279
|
-
mkdirSync(dirname(outFile), { recursive: true })
|
|
280
|
-
writeGeneratedFile(
|
|
281
|
-
outFile,
|
|
282
|
-
frameworkLockedPageRouteSource(locked.routeId, locked.importFrom, locked.rel),
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
for (const locked of FRAMEWORK_LOCKED_API_ROUTES) {
|
|
287
|
-
const outFile = join(routesDir, locked.rel)
|
|
288
|
-
mkdirSync(dirname(outFile), { recursive: true })
|
|
289
|
-
writeGeneratedFile(
|
|
290
|
-
outFile,
|
|
291
|
-
apiRouteFileSource(locked.routeId, locked.importFrom),
|
|
292
|
-
)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
reconcileGeneratedRoutes(appDir, routesDir, desiredRoutes)
|
|
296
|
-
|
|
297
|
-
// ── 9. wrangler.jsonc para deploy en Cloudflare Workers ──
|
|
298
|
-
const workerName = config.deploy?.name ?? slugify(config.domain ?? 'ecomiq-store')
|
|
299
|
-
writeGeneratedFile(
|
|
300
|
-
join(appDir, 'wrangler.jsonc'),
|
|
301
|
-
JSON.stringify(
|
|
302
|
-
{
|
|
303
|
-
$schema: 'node_modules/wrangler/config-schema.json',
|
|
304
|
-
name: workerName,
|
|
305
|
-
main: 'src/server.ts',
|
|
306
|
-
compatibility_date: '2026-01-01',
|
|
307
|
-
compatibility_flags: ['nodejs_compat'],
|
|
308
|
-
observability: {
|
|
309
|
-
enabled: false,
|
|
310
|
-
head_sampling_rate: 1,
|
|
311
|
-
logs: {
|
|
312
|
-
enabled: true,
|
|
313
|
-
head_sampling_rate: 1,
|
|
314
|
-
persist: true,
|
|
315
|
-
invocation_logs: true,
|
|
316
|
-
},
|
|
317
|
-
traces: {
|
|
318
|
-
enabled: false,
|
|
319
|
-
persist: true,
|
|
320
|
-
head_sampling_rate: 1,
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
...(config.deploy?.routes ? { routes: config.deploy.routes } : {}),
|
|
324
|
-
vars: {...(config.env ?? {}), BETTER_AUTH_TRUSTED_HOSTS: "localhost,localhost:*,127.0.0.1,127.0.0.1:*,*.ecomiq.app,ecomiq.app,*.juanpablocs.workers.dev,*.aecca.workers.dev"},
|
|
325
|
-
},
|
|
326
|
-
null,
|
|
327
|
-
2,
|
|
328
|
-
),
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
// ── 10. .gitignore para que .ecomiq no se versione dentro de sí mismo ──
|
|
332
|
-
writeGeneratedFile(join(appDir, '.gitignore'), '*\n')
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function watchPages({
|
|
336
|
-
cwd,
|
|
337
|
-
appDir,
|
|
338
|
-
frameworkRoot,
|
|
339
|
-
config,
|
|
340
|
-
debounceMs = 75,
|
|
341
|
-
/** Intervalo de polling (ms) como respaldo cuando `fs.watch` no emite eventos (p. ej. sandbox). */
|
|
342
|
-
pollMs = 0,
|
|
343
|
-
onGenerate = () => {},
|
|
344
|
-
onError = () => {},
|
|
345
|
-
}) {
|
|
346
|
-
const pagesDir = resolve(cwd, 'pages')
|
|
347
|
-
if (!existsSync(pagesDir)) {
|
|
348
|
-
throw new Error('No existe la carpeta pages/. Crea al menos pages/index.tsx')
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
let closed = false
|
|
352
|
-
let dirty = false
|
|
353
|
-
let running = false
|
|
354
|
-
let timer
|
|
355
|
-
let lastPagesSignature = pagesDirSignature(pagesDir)
|
|
356
|
-
|
|
357
|
-
async function flush() {
|
|
358
|
-
if (running || closed) return
|
|
359
|
-
running = true
|
|
360
|
-
|
|
361
|
-
while (dirty && !closed) {
|
|
362
|
-
dirty = false
|
|
363
|
-
try {
|
|
364
|
-
await generateApp({ cwd, appDir, frameworkRoot, config })
|
|
365
|
-
lastPagesSignature = pagesDirSignature(pagesDir)
|
|
366
|
-
onGenerate()
|
|
367
|
-
} catch (error) {
|
|
368
|
-
onError(error instanceof Error ? error : new Error(String(error)))
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
running = false
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function schedule() {
|
|
376
|
-
if (closed) return
|
|
377
|
-
if (timer) clearTimeout(timer)
|
|
378
|
-
timer = setTimeout(() => {
|
|
379
|
-
timer = undefined
|
|
380
|
-
dirty = true
|
|
381
|
-
void flush()
|
|
382
|
-
}, debounceMs)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const watcher = watchFileSystem(pagesDir, { recursive: true }, schedule)
|
|
386
|
-
watcher.on('error', (error) => onError(error))
|
|
387
|
-
|
|
388
|
-
let pollTimer
|
|
389
|
-
if (pollMs > 0) {
|
|
390
|
-
pollTimer = setInterval(() => {
|
|
391
|
-
if (closed) return
|
|
392
|
-
const next = pagesDirSignature(pagesDir)
|
|
393
|
-
if (next !== lastPagesSignature) {
|
|
394
|
-
lastPagesSignature = next
|
|
395
|
-
schedule()
|
|
396
|
-
}
|
|
397
|
-
}, pollMs)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return {
|
|
401
|
-
close() {
|
|
402
|
-
closed = true
|
|
403
|
-
dirty = false
|
|
404
|
-
if (timer) clearTimeout(timer)
|
|
405
|
-
if (pollTimer) clearInterval(pollTimer)
|
|
406
|
-
watcher.close()
|
|
407
|
-
},
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** Firma ligera de `pages/` para detectar altas/bajas/renombres sin depender de `fs.watch`. */
|
|
412
|
-
function pagesDirSignature(pagesDir) {
|
|
413
|
-
const parts = []
|
|
414
|
-
for (const entry of listPageEntries(pagesDir)) {
|
|
415
|
-
const full = join(pagesDir, entry.rel)
|
|
416
|
-
parts.push(`${entry.rel}:${statSync(full).mtimeMs}`)
|
|
417
|
-
}
|
|
418
|
-
return parts.sort().join('|')
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/** Lista pages UI (.tsx/.jsx) y API server routes (.ts) de forma recursiva. */
|
|
422
|
-
function listPageEntries(pagesDir, prefix = '') {
|
|
423
|
-
const entries = []
|
|
424
|
-
const dir = prefix ? join(pagesDir, prefix) : pagesDir
|
|
425
|
-
for (const name of readdirSync(dir).sort()) {
|
|
426
|
-
if (
|
|
427
|
-
name.startsWith('.') ||
|
|
428
|
-
name === '__tests__' ||
|
|
429
|
-
/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(name)
|
|
430
|
-
) continue
|
|
431
|
-
const rel = prefix ? `${prefix}/${name}` : name
|
|
432
|
-
const full = join(pagesDir, rel)
|
|
433
|
-
if (statSync(full).isDirectory()) {
|
|
434
|
-
entries.push(...listPageEntries(pagesDir, rel))
|
|
435
|
-
continue
|
|
436
|
-
}
|
|
437
|
-
if (/\.(tsx|jsx)$/.test(name)) entries.push({ kind: 'page', rel })
|
|
438
|
-
else if (/\.ts$/.test(name) && !name.endsWith('.d.ts')) entries.push({ kind: 'api', rel })
|
|
439
|
-
}
|
|
440
|
-
return entries
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function reconcileGeneratedRoutes(appDir, routesDir, desiredRoutes) {
|
|
444
|
-
const manifestPath = join(appDir, ROUTE_MANIFEST_FILE)
|
|
445
|
-
const previousRoutes = readRouteManifest(manifestPath)
|
|
446
|
-
const desired = new Set(desiredRoutes)
|
|
447
|
-
|
|
448
|
-
for (const relativePath of previousRoutes) {
|
|
449
|
-
if (desired.has(relativePath)) continue
|
|
450
|
-
const outputPath = ownedRoutePath(routesDir, relativePath)
|
|
451
|
-
if (outputPath) rmSync(outputPath, { force: true })
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
writeGeneratedFile(
|
|
455
|
-
manifestPath,
|
|
456
|
-
JSON.stringify({ version: 1, routes: desiredRoutes }, null, 2) + '\n',
|
|
457
|
-
)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function readRouteManifest(manifestPath) {
|
|
461
|
-
if (!existsSync(manifestPath)) return []
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
465
|
-
if (manifest?.version !== 1 || !Array.isArray(manifest.routes)) return []
|
|
466
|
-
return manifest.routes.filter((route) => typeof route === 'string')
|
|
467
|
-
} catch {
|
|
468
|
-
return []
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function ownedRoutePath(routesDir, relativePath) {
|
|
473
|
-
if (
|
|
474
|
-
relativePath.length === 0 ||
|
|
475
|
-
isAbsolute(relativePath) ||
|
|
476
|
-
relativePath.split('/').includes('..')
|
|
477
|
-
) {
|
|
478
|
-
return null
|
|
479
|
-
}
|
|
480
|
-
const outputPath = resolve(routesDir, relativePath)
|
|
481
|
-
if (!outputPath.startsWith(resolve(routesDir) + sep)) return null
|
|
482
|
-
return outputPath
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* 'products.$productId.tsx' → '/products/$productId'
|
|
487
|
-
* 'api/$.ts' → '/api/$'
|
|
488
|
-
* 'index.tsx' → '/'
|
|
489
|
-
*/
|
|
490
|
-
function fileToRouteId(relPath) {
|
|
491
|
-
const withoutExt = relPath.replace(/\.(tsx|jsx|ts)$/, '')
|
|
492
|
-
if (withoutExt === 'index') return '/'
|
|
493
|
-
|
|
494
|
-
const segments = []
|
|
495
|
-
for (const part of withoutExt.split('/')) {
|
|
496
|
-
const bits = part.split('.').map((s) => (s === 'index' ? '' : s))
|
|
497
|
-
segments.push(...bits.filter(Boolean))
|
|
498
|
-
}
|
|
499
|
-
return '/' + segments.join('/') + (withoutExt.endsWith('.index') ? '/' : '')
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/** Relativo desde routes/<rel> hasta src/app-config. */
|
|
503
|
-
function appConfigImportFrom(relPath) {
|
|
504
|
-
const nestDepth = relPath.split('/').length - 1
|
|
505
|
-
return '../'.repeat(nestDepth + 1) + 'app-config'
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/** Código de una ruta interna que envuelve la página del usuario. */
|
|
509
|
-
function routeFileSource(routeId, importPath, relPath) {
|
|
510
|
-
const appConfigImport = appConfigImportFrom(relPath)
|
|
511
|
-
|
|
512
|
-
return (
|
|
513
|
-
HEADER +
|
|
514
|
-
`import { createFileRoute } from '@tanstack/react-router'\n` +
|
|
515
|
-
`import appConfig from '${appConfigImport}'\n` +
|
|
516
|
-
`import pageModule from '${importPath}'\n` +
|
|
517
|
-
`import { pageToRouteOptions, PageBoundary } from '@ecomiq/storefront/internal/page'\n\n` +
|
|
518
|
-
`export const Route = createFileRoute('${routeId}')({\n` +
|
|
519
|
-
` ...pageToRouteOptions(pageModule, appConfig),\n` +
|
|
520
|
-
` component: PageComponent,\n` +
|
|
521
|
-
`})\n\n` +
|
|
522
|
-
`function PageComponent() {\n` +
|
|
523
|
-
` const data = Route.useLoaderData()\n` +
|
|
524
|
-
` const params = Route.useParams()\n` +
|
|
525
|
-
` const search = Route.useSearch()\n` +
|
|
526
|
-
` return <PageBoundary page={pageModule} data={data} params={params} search={search} />\n` +
|
|
527
|
-
`}\n`
|
|
528
|
-
)
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/** Página cerrada del framework (import desde package, no desde pages/ del usuario). */
|
|
532
|
-
function frameworkLockedPageRouteSource(routeId, importFrom, relPath) {
|
|
533
|
-
const appConfigImport = appConfigImportFrom(relPath)
|
|
534
|
-
|
|
535
|
-
return (
|
|
536
|
-
HEADER +
|
|
537
|
-
`import { createFileRoute } from '@tanstack/react-router'\n` +
|
|
538
|
-
`import appConfig from '${appConfigImport}'\n` +
|
|
539
|
-
`import pageModule from '${importFrom}'\n` +
|
|
540
|
-
`import { pageToRouteOptions, PageBoundary } from '@ecomiq/storefront/internal/page'\n\n` +
|
|
541
|
-
`export const Route = createFileRoute('${routeId}')({\n` +
|
|
542
|
-
` ...pageToRouteOptions(pageModule, appConfig),\n` +
|
|
543
|
-
` component: PageComponent,\n` +
|
|
544
|
-
`})\n\n` +
|
|
545
|
-
`function PageComponent() {\n` +
|
|
546
|
-
` const data = Route.useLoaderData()\n` +
|
|
547
|
-
` const params = Route.useParams()\n` +
|
|
548
|
-
` const search = Route.useSearch()\n` +
|
|
549
|
-
` return <PageBoundary page={pageModule} data={data} params={params} search={search} />\n` +
|
|
550
|
-
`}\n`
|
|
551
|
-
)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function assertNoFrameworkPageOverrides(pageEntries) {
|
|
555
|
-
const reserved = new Map(
|
|
556
|
-
FRAMEWORK_LOCKED_PAGES.map((page) => [page.routeId, page.rel]),
|
|
557
|
-
)
|
|
558
|
-
// Overrides / alias legados
|
|
559
|
-
reserved.set('/checkout', 'checkout.tsx')
|
|
560
|
-
|
|
561
|
-
for (const entry of pageEntries) {
|
|
562
|
-
const routeId = fileToRouteId(entry.rel)
|
|
563
|
-
if (!reserved.has(routeId)) continue
|
|
564
|
-
throw new Error(
|
|
565
|
-
`La ruta ${routeId} es reservada por Ecomiq (página cerrada). ` +
|
|
566
|
-
`Elimina pages/${entry.rel}; vive en @ecomiq/storefront (checkout, thank-you, CMS).`,
|
|
567
|
-
)
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function assertNoFrameworkApiOverrides(apiEntries) {
|
|
572
|
-
const reserved = new Map(
|
|
573
|
-
FRAMEWORK_LOCKED_API_ROUTES.map((route) => [route.routeId, route.rel]),
|
|
574
|
-
)
|
|
575
|
-
|
|
576
|
-
for (const entry of apiEntries) {
|
|
577
|
-
const routeId = fileToRouteId(entry.rel)
|
|
578
|
-
if (!reserved.has(routeId)) continue
|
|
579
|
-
throw new Error(
|
|
580
|
-
`La ruta ${routeId} es reservada por Ecomiq. ` +
|
|
581
|
-
`Elimina pages/${entry.rel}; ya la genera @ecomiq/storefront ` +
|
|
582
|
-
`(proxy BFF /api/$ y Better Auth /api/auth/$). ` +
|
|
583
|
-
`Puedes añadir otras rutas en pages/api/.`,
|
|
584
|
-
)
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** Server route: reexporta GET/POST/... del módulo del usuario. */
|
|
589
|
-
function apiRouteFileSource(routeId, importPath) {
|
|
590
|
-
return (
|
|
591
|
-
HEADER +
|
|
592
|
-
`import { createFileRoute } from '@tanstack/react-router'\n` +
|
|
593
|
-
`import * as api from '${importPath}'\n\n` +
|
|
594
|
-
`export const Route = createFileRoute('${routeId}')({\n` +
|
|
595
|
-
` server: {\n` +
|
|
596
|
-
` handlers: {\n` +
|
|
597
|
-
` ...(api.GET ? { GET: api.GET } : {}),\n` +
|
|
598
|
-
` ...(api.POST ? { POST: api.POST } : {}),\n` +
|
|
599
|
-
` ...(api.PUT ? { PUT: api.PUT } : {}),\n` +
|
|
600
|
-
` ...(api.PATCH ? { PATCH: api.PATCH } : {}),\n` +
|
|
601
|
-
` ...(api.DELETE ? { DELETE: api.DELETE } : {}),\n` +
|
|
602
|
-
` },\n` +
|
|
603
|
-
` },\n` +
|
|
604
|
-
`})\n`
|
|
605
|
-
)
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function buildThemeOverrides(config) {
|
|
609
|
-
const theme = config.theme ?? {}
|
|
610
|
-
const map = {
|
|
611
|
-
primary: '--primary',
|
|
612
|
-
primaryForeground: '--primary-foreground',
|
|
613
|
-
radius: '--radius',
|
|
614
|
-
background: '--background',
|
|
615
|
-
foreground: '--foreground',
|
|
616
|
-
accent: '--accent',
|
|
617
|
-
}
|
|
618
|
-
const lines = Object.entries(map)
|
|
619
|
-
.filter(([key]) => theme[key])
|
|
620
|
-
.map(([key, cssVar]) => ` ${cssVar}: ${theme[key]};`)
|
|
621
|
-
if (lines.length === 0) return ''
|
|
622
|
-
return `:root {\n${lines.join('\n')}\n}`
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function writeTemplate(frameworkRoot, targetDir, name) {
|
|
626
|
-
const src = resolve(frameworkRoot, 'templates', name)
|
|
627
|
-
writeGeneratedFile(join(targetDir, name), readFileSync(src, 'utf8'))
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function writeGeneratedFile(path, contents) {
|
|
631
|
-
if (existsSync(path) && readFileSync(path, 'utf8') === contents) return
|
|
632
|
-
mkdirSync(dirname(path), { recursive: true })
|
|
633
|
-
writeFileSync(path, contents)
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Crea `.ecomiq/node_modules` con symlinks a las dependencias del framework.
|
|
638
|
-
*
|
|
639
|
-
* Workerd (Cloudflare Vite plugin) resuelve desde la raíz `.ecomiq`, así que
|
|
640
|
-
* no basta con el hoisting del proyecto: hay que materializar los packages
|
|
641
|
-
* ahí. En installs npm/pnpm/yarn las deps suelen vivir en el node_modules del
|
|
642
|
-
* proyecto (o en el store de pnpm), no bajo `@ecomiq/storefront/node_modules`.
|
|
643
|
-
*/
|
|
644
|
-
function linkFrameworkDependencies(appDir, frameworkRoot, projectRoot = dirname(appDir)) {
|
|
645
|
-
const target = join(appDir, 'node_modules')
|
|
646
|
-
mkdirSync(target, { recursive: true })
|
|
647
|
-
|
|
648
|
-
linkInto(join(target, '@ecomiq'), 'storefront', frameworkRoot)
|
|
649
|
-
const legacyFramework = join(target, '@ecomiq', 'framework')
|
|
650
|
-
try {
|
|
651
|
-
if (lstatSync(legacyFramework).isSymbolicLink()) unlinkSync(legacyFramework)
|
|
652
|
-
} catch {
|
|
653
|
-
/* no existía */
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const resolveFrom = [
|
|
657
|
-
join(frameworkRoot, 'package.json'),
|
|
658
|
-
join(projectRoot, 'package.json'),
|
|
659
|
-
join(frameworkRoot, 'src', 'codegen.mjs'),
|
|
660
|
-
]
|
|
661
|
-
|
|
662
|
-
const manifestPath = join(frameworkRoot, 'package.json')
|
|
663
|
-
let depNames = []
|
|
664
|
-
try {
|
|
665
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
666
|
-
depNames = [
|
|
667
|
-
...Object.keys(manifest.dependencies ?? {}),
|
|
668
|
-
...Object.keys(manifest.peerDependencies ?? {}),
|
|
669
|
-
]
|
|
670
|
-
} catch {
|
|
671
|
-
depNames = []
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Transitive crítico que Vite/TanStack piden explícitamente en SSR.
|
|
675
|
-
for (const extra of ['@tanstack/react-store', '@tanstack/history', '@tanstack/router-core']) {
|
|
676
|
-
if (!depNames.includes(extra)) depNames.push(extra)
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
for (const name of depNames) {
|
|
680
|
-
const pkgDir = resolvePackageDir(name, resolveFrom)
|
|
681
|
-
if (!pkgDir) continue
|
|
682
|
-
linkPackage(target, name, pkgDir)
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Fallback: mirror de node_modules locales (monorepo / nested installs).
|
|
686
|
-
for (const source of [join(frameworkRoot, 'node_modules'), join(projectRoot, 'node_modules')]) {
|
|
687
|
-
linkNodeModulesTree(source, target)
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function linkPackage(targetRoot, packageName, packageDir) {
|
|
692
|
-
if (packageName.startsWith('@')) {
|
|
693
|
-
const [scope, name] = packageName.split('/')
|
|
694
|
-
if (!scope || !name) return
|
|
695
|
-
linkInto(join(targetRoot, scope), name, packageDir)
|
|
696
|
-
return
|
|
697
|
-
}
|
|
698
|
-
linkInto(targetRoot, packageName, packageDir)
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function linkNodeModulesTree(source, target) {
|
|
702
|
-
if (!existsSync(source)) return
|
|
703
|
-
for (const entry of readdirSync(source)) {
|
|
704
|
-
if (entry === '.bin' || entry.startsWith('.')) continue
|
|
705
|
-
if (entry.startsWith('@')) {
|
|
706
|
-
const scopeDir = join(source, entry)
|
|
707
|
-
if (!statSync(scopeDir).isDirectory()) continue
|
|
708
|
-
for (const scoped of readdirSync(scopeDir)) {
|
|
709
|
-
linkInto(join(target, entry), scoped, join(scopeDir, scoped))
|
|
710
|
-
}
|
|
711
|
-
} else {
|
|
712
|
-
linkInto(target, entry, join(source, entry))
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Resuelve el directorio raíz de un package usando la resolución de Node
|
|
719
|
-
* (funciona con hoisting npm/yarn y el store de pnpm).
|
|
720
|
-
*/
|
|
721
|
-
function resolvePackageDir(packageName, fromFiles) {
|
|
722
|
-
for (const fromFile of fromFiles) {
|
|
723
|
-
if (!existsSync(fromFile)) continue
|
|
724
|
-
try {
|
|
725
|
-
const require = createRequire(fromFile)
|
|
726
|
-
try {
|
|
727
|
-
return dirname(require.resolve(`${packageName}/package.json`))
|
|
728
|
-
} catch {
|
|
729
|
-
const entry = require.resolve(packageName)
|
|
730
|
-
return findPackageRoot(entry, packageName)
|
|
731
|
-
}
|
|
732
|
-
} catch {
|
|
733
|
-
/* probar siguiente origen */
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return null
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function findPackageRoot(startPath, packageName) {
|
|
740
|
-
let dir = dirname(startPath)
|
|
741
|
-
while (true) {
|
|
742
|
-
const pkgPath = join(dir, 'package.json')
|
|
743
|
-
if (existsSync(pkgPath)) {
|
|
744
|
-
try {
|
|
745
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
746
|
-
if (pkg.name === packageName) return dir
|
|
747
|
-
} catch {
|
|
748
|
-
/* ignore */
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
const parent = dirname(dir)
|
|
752
|
-
if (parent === dir) return null
|
|
753
|
-
dir = parent
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function linkInto(dir, name, targetPath) {
|
|
758
|
-
mkdirSync(dir, { recursive: true })
|
|
759
|
-
const linkPath = join(dir, name)
|
|
760
|
-
|
|
761
|
-
// Target absoluto y ya resuelto: los symlinks relativos se rompen cuando un
|
|
762
|
-
// directorio padre es a su vez un symlink (p. ej. /var → /private/var en macOS).
|
|
763
|
-
let destination
|
|
764
|
-
try {
|
|
765
|
-
destination = realpathSync(targetPath)
|
|
766
|
-
} catch {
|
|
767
|
-
destination = resolve(targetPath)
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
try {
|
|
771
|
-
const current = lstatSync(linkPath)
|
|
772
|
-
if (current.isSymbolicLink()) {
|
|
773
|
-
let resolved
|
|
774
|
-
try {
|
|
775
|
-
resolved = realpathSync(linkPath)
|
|
776
|
-
} catch {
|
|
777
|
-
resolved = resolve(dir, readlinkSync(linkPath))
|
|
778
|
-
}
|
|
779
|
-
if (resolved === destination) return
|
|
780
|
-
unlinkSync(linkPath)
|
|
781
|
-
} else if (current.isDirectory() || current.isFile()) {
|
|
782
|
-
return
|
|
783
|
-
}
|
|
784
|
-
} catch {
|
|
785
|
-
/* no existe aún */
|
|
786
|
-
}
|
|
787
|
-
try {
|
|
788
|
-
symlinkSync(destination, linkPath, 'junction')
|
|
789
|
-
} catch {
|
|
790
|
-
/* FS sin symlinks: la resolución cae al hoisting */
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function slugify(s) {
|
|
795
|
-
return s
|
|
796
|
-
.toLowerCase()
|
|
797
|
-
.normalize('NFD')
|
|
798
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
799
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
800
|
-
.replace(/^-|-$/g, '')
|
|
801
|
-
}
|