@ecomiq/storefront 2.0.5 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ecomiq.mjs +1 -2
- package/package.json +2 -1
- package/src/codegen.mjs +6 -1
- package/src/pages/pages.$pageSlug.tsx +74 -0
- package/src/server.ts +5 -0
- package/src/vite-inline.mjs +23 -6
package/bin/ecomiq.mjs
CHANGED
|
@@ -89,8 +89,7 @@ export async function runCli(command = process.argv[2] ?? 'help') {
|
|
|
89
89
|
runtime,
|
|
90
90
|
})
|
|
91
91
|
banner(
|
|
92
|
-
`dev · ${config.name ?? 'tienda'} ${c.dim('http://localhost:' + port)}
|
|
93
|
-
(runtime === 'workerd' ? c.dim(' · runtime workerd') : ''),
|
|
92
|
+
`dev · ${config.name ?? 'tienda'} ${c.dim(`· runtime ${runtime}`)} ${c.dim('http://localhost:' + port)}`,
|
|
94
93
|
)
|
|
95
94
|
const server = await createServer({
|
|
96
95
|
...viteConfig,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecomiq/storefront",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Micro framework SSR de Ecomiq para storefronts. Páginas, datos y deploy sin configurar nada.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"./internal/checkout": "./src/pages/checkout.tsx",
|
|
42
42
|
"./internal/checkout-session": "./src/pages/checkout.$checkoutId.tsx",
|
|
43
43
|
"./internal/thank-you": "./src/pages/thank-you.tsx",
|
|
44
|
+
"./internal/cms-page": "./src/pages/pages.$pageSlug.tsx",
|
|
44
45
|
"./package.json": "./package.json"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
package/src/codegen.mjs
CHANGED
|
@@ -57,6 +57,11 @@ const FRAMEWORK_LOCKED_PAGES = [
|
|
|
57
57
|
routeId: '/thank-you',
|
|
58
58
|
importFrom: '@ecomiq/storefront/internal/thank-you',
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
rel: 'pages.$pageSlug.tsx',
|
|
62
|
+
routeId: '/pages/$pageSlug',
|
|
63
|
+
importFrom: '@ecomiq/storefront/internal/cms-page',
|
|
64
|
+
},
|
|
60
65
|
]
|
|
61
66
|
|
|
62
67
|
export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
@@ -487,7 +492,7 @@ function assertNoFrameworkPageOverrides(pageEntries) {
|
|
|
487
492
|
if (!reserved.has(routeId)) continue
|
|
488
493
|
throw new Error(
|
|
489
494
|
`La ruta ${routeId} es reservada por Ecomiq (página cerrada). ` +
|
|
490
|
-
`Elimina pages/${entry.rel};
|
|
495
|
+
`Elimina pages/${entry.rel}; vive en @ecomiq/storefront (checkout, thank-you, CMS).`,
|
|
491
496
|
)
|
|
492
497
|
}
|
|
493
498
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Página CMS cerrada (`/pages/$pageSlug`).
|
|
3
|
+
* Resuelve contenido publicado vía GET /v1/pages/{slug}.
|
|
4
|
+
*/
|
|
5
|
+
import type { CmsPageDetailsDto } from '@ecomiq/core'
|
|
6
|
+
import {
|
|
7
|
+
definePage,
|
|
8
|
+
sanitizeHtml,
|
|
9
|
+
serverApi,
|
|
10
|
+
stripHtml,
|
|
11
|
+
z,
|
|
12
|
+
} from '../index'
|
|
13
|
+
import { StorefrontError } from '../ui'
|
|
14
|
+
|
|
15
|
+
const ContentPageSettingsSchema = z.object({
|
|
16
|
+
title: z.string().optional(),
|
|
17
|
+
html: z.string().optional(),
|
|
18
|
+
seo: z.object({
|
|
19
|
+
title: z.string().optional(),
|
|
20
|
+
description: z.string().optional(),
|
|
21
|
+
}).optional(),
|
|
22
|
+
}).passthrough()
|
|
23
|
+
|
|
24
|
+
type ContentPageData = {
|
|
25
|
+
page: CmsPageDetailsDto
|
|
26
|
+
content: z.infer<typeof ContentPageSettingsSchema>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default definePage<ContentPageData, { pageSlug: string }>({
|
|
30
|
+
loader: async ({ params }) => {
|
|
31
|
+
const page = await serverApi.getPageBySlug(params.pageSlug)
|
|
32
|
+
if (page.status !== 'published') throw new Error('Página no publicada')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
page,
|
|
36
|
+
content: ContentPageSettingsSchema.parse(page.settings ?? {}),
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
meta: ({ data }) => ({
|
|
41
|
+
title: data.content.seo?.title ?? data.content.title ?? data.page.name,
|
|
42
|
+
description: data.content.seo?.description ?? (
|
|
43
|
+
data.content.html
|
|
44
|
+
? stripHtml(data.content.html).slice(0, 155)
|
|
45
|
+
: undefined
|
|
46
|
+
),
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
error: () => (
|
|
50
|
+
<StorefrontError
|
|
51
|
+
title="Página no disponible"
|
|
52
|
+
description="El contenido no existe o todavía no está publicado."
|
|
53
|
+
/>
|
|
54
|
+
),
|
|
55
|
+
|
|
56
|
+
component: ({ data }) => (
|
|
57
|
+
<article className="mx-auto w-full max-w-4xl px-4 py-10 sm:py-14">
|
|
58
|
+
<header className="mb-8 border-b pb-8">
|
|
59
|
+
<h1 className="text-balance text-3xl font-semibold tracking-[-0.02em] md:text-4xl">
|
|
60
|
+
{data.content.title ?? data.page.name}
|
|
61
|
+
</h1>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
{data.content.html ? (
|
|
65
|
+
<div
|
|
66
|
+
className="prose prose-neutral max-w-none text-foreground prose-headings:font-semibold prose-a:text-primary"
|
|
67
|
+
dangerouslySetInnerHTML={{ __html: sanitizeHtml(data.content.html) }}
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
<p className="text-muted-foreground">Contenido no disponible.</p>
|
|
71
|
+
)}
|
|
72
|
+
</article>
|
|
73
|
+
),
|
|
74
|
+
})
|
package/src/server.ts
CHANGED
|
@@ -86,6 +86,10 @@ const getCollectionBySlugFn = createServerFn({ method: 'GET' })
|
|
|
86
86
|
.validator(z.object({ slug: z.string().min(1) }))
|
|
87
87
|
.handler(({ data }) => getStorefrontClient().getCollectionBySlug(data.slug))
|
|
88
88
|
|
|
89
|
+
const getPageBySlugFn = createServerFn({ method: 'GET' })
|
|
90
|
+
.validator(z.object({ slug: z.string().min(1) }))
|
|
91
|
+
.handler(({ data }) => getStorefrontClient().getPageBySlug(data.slug))
|
|
92
|
+
|
|
89
93
|
const getProductsFn = createServerFn({ method: 'GET' })
|
|
90
94
|
.validator(ProductsParamsSchema)
|
|
91
95
|
.handler(({ data }) => getStorefrontClient().getProducts(data))
|
|
@@ -219,6 +223,7 @@ export const serverApi = {
|
|
|
219
223
|
getStoreId: () => getStoreIdFn(),
|
|
220
224
|
getCollections: () => getCollectionsFn(),
|
|
221
225
|
getCollectionBySlug: (slug: string) => getCollectionBySlugFn({ data: { slug } }),
|
|
226
|
+
getPageBySlug: (slug: string) => getPageBySlugFn({ data: { slug } }),
|
|
222
227
|
getProducts: (
|
|
223
228
|
params: ProductsParamsInput = {},
|
|
224
229
|
options: { signal?: AbortSignal } = {},
|
package/src/vite-inline.mjs
CHANGED
|
@@ -59,11 +59,24 @@ function sanitizeHtmlClientBoundary() {
|
|
|
59
59
|
* TanStack Start marca `@tanstack/react-router` como `resolve.external` fuera de
|
|
60
60
|
* su monorepo. Cloudflare Vite plugin no lo tolera y workerd acaba ejecutando
|
|
61
61
|
* CJS con `require` → "require is not defined".
|
|
62
|
+
*
|
|
63
|
+
* Solo aplica en runtime `workerd`. En `node`, React y otras deps CJS deben
|
|
64
|
+
* quedar external: `noExternal` las pasa por el module runner ESM de Vite y
|
|
65
|
+
* falla con "module is not defined".
|
|
62
66
|
* @see https://github.com/TanStack/router/issues/5291
|
|
63
67
|
*/
|
|
64
|
-
function
|
|
68
|
+
function bundleSsrDependenciesForWorkerd() {
|
|
65
69
|
return {
|
|
66
|
-
name: 'ecomiq:
|
|
70
|
+
name: 'ecomiq:ssr-no-external',
|
|
71
|
+
configEnvironment(name) {
|
|
72
|
+
if (name !== 'ssr') return
|
|
73
|
+
return {
|
|
74
|
+
resolve: {
|
|
75
|
+
external: [],
|
|
76
|
+
noExternal: true,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
},
|
|
67
80
|
configResolved(config) {
|
|
68
81
|
const ssr = config.environments?.ssr
|
|
69
82
|
if (ssr?.resolve) {
|
|
@@ -99,10 +112,11 @@ export function createViteConfig({ appDir, config, projectRoot, runtime }) {
|
|
|
99
112
|
|
|
100
113
|
const resolvedRuntime = resolveDevRuntime({ config, runtime })
|
|
101
114
|
|
|
102
|
-
const plugins = [
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
const plugins = [tailwindcss()]
|
|
116
|
+
|
|
117
|
+
if (resolvedRuntime === 'workerd') {
|
|
118
|
+
plugins.unshift(bundleSsrDependenciesForWorkerd())
|
|
119
|
+
}
|
|
106
120
|
|
|
107
121
|
// workerd emula Cloudflare Workers (opt-in en `ecomiq dev`).
|
|
108
122
|
// Default es Node; build/deploy siempre usan workerd.
|
|
@@ -143,6 +157,9 @@ export function createViteConfig({ appDir, config, projectRoot, runtime }) {
|
|
|
143
157
|
plugins,
|
|
144
158
|
build: {
|
|
145
159
|
cssCodeSplit: false,
|
|
160
|
+
commonjsOptions: {
|
|
161
|
+
transformMixedEsModules: true,
|
|
162
|
+
},
|
|
146
163
|
rollupOptions: {
|
|
147
164
|
output: {
|
|
148
165
|
assetFileNames: (assetInfo) => {
|