@ecomiq/storefront 2.0.0 → 2.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/README.md +7 -0
- package/bin/ecomiq.mjs +14 -3
- package/package.json +1 -1
- package/src/codegen.mjs +115 -14
- package/src/vite-inline.mjs +43 -3
package/README.md
CHANGED
|
@@ -47,4 +47,11 @@ npx ecomiq build
|
|
|
47
47
|
npx ecomiq deploy
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
Si actualizas el package y `.ecomiq` quedó de una versión anterior:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx ecomiq clean
|
|
54
|
+
npx ecomiq dev
|
|
55
|
+
```
|
|
56
|
+
|
|
50
57
|
Ver el monorepo [ecomiq-storefront](https://github.com/ecomiq/ecomiq-storefront) para la app de referencia y más documentación.
|
package/bin/ecomiq.mjs
CHANGED
|
@@ -82,7 +82,18 @@ export async function runCli(command = process.argv[2] ?? 'help') {
|
|
|
82
82
|
const port = config.dev?.port ?? 3000
|
|
83
83
|
banner(`dev · ${config.name ?? 'tienda'} ${c.dim('http://localhost:' + port)}`)
|
|
84
84
|
const { createServer } = await import('vite')
|
|
85
|
-
const
|
|
85
|
+
const viteConfig = createViteConfig({ appDir, config, projectRoot: cwd })
|
|
86
|
+
const server = await createServer({
|
|
87
|
+
...viteConfig,
|
|
88
|
+
server: {
|
|
89
|
+
...viteConfig.server,
|
|
90
|
+
port,
|
|
91
|
+
fs: {
|
|
92
|
+
...viteConfig.server?.fs,
|
|
93
|
+
allow: [...new Set([...(viteConfig.server?.fs?.allow ?? []), '..', cwd, frameworkRoot])],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
})
|
|
86
97
|
await server.listen()
|
|
87
98
|
const pageWatcher = watchPages({
|
|
88
99
|
cwd,
|
|
@@ -104,7 +115,7 @@ export async function runCli(command = process.argv[2] ?? 'help') {
|
|
|
104
115
|
const config = await prepare()
|
|
105
116
|
banner(`build · Cloudflare Workers`)
|
|
106
117
|
const { createBuilder } = await import('vite')
|
|
107
|
-
const builder = await createBuilder(createViteConfig({ appDir, config }))
|
|
118
|
+
const builder = await createBuilder(createViteConfig({ appDir, config, projectRoot: cwd }))
|
|
108
119
|
await builder.buildApp()
|
|
109
120
|
console.log(c.green('\n ✔ Build listo en .ecomiq/dist\n'))
|
|
110
121
|
break
|
|
@@ -113,7 +124,7 @@ export async function runCli(command = process.argv[2] ?? 'help') {
|
|
|
113
124
|
const config = await prepare()
|
|
114
125
|
banner(`deploy · Cloudflare Workers`)
|
|
115
126
|
const { createBuilder } = await import('vite')
|
|
116
|
-
const builder = await createBuilder(createViteConfig({ appDir, config }))
|
|
127
|
+
const builder = await createBuilder(createViteConfig({ appDir, config, projectRoot: cwd }))
|
|
117
128
|
await builder.buildApp()
|
|
118
129
|
// @cloudflare/vite-plugin emite la config final del Worker junto al build
|
|
119
130
|
const built = resolve(appDir, 'dist', 'server', 'wrangler.json')
|
package/package.json
CHANGED
package/src/codegen.mjs
CHANGED
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
readlinkSync,
|
|
31
31
|
unlinkSync,
|
|
32
32
|
statSync,
|
|
33
|
+
realpathSync,
|
|
33
34
|
} from 'node:fs'
|
|
35
|
+
import { createRequire } from 'node:module'
|
|
34
36
|
import { resolve, join, relative, dirname, isAbsolute, sep } from 'node:path'
|
|
35
37
|
|
|
36
38
|
const HEADER = '/* Generado por Ecomiq — no editar. */\n'
|
|
@@ -68,7 +70,7 @@ export async function generateApp({ cwd, appDir, frameworkRoot, config }) {
|
|
|
68
70
|
mkdirSync(routesDir, { recursive: true })
|
|
69
71
|
|
|
70
72
|
// ── 1. dependencias internas resolubles desde .ecomiq (soporta npm, pnpm y monorepos) ──
|
|
71
|
-
linkFrameworkDependencies(appDir, frameworkRoot)
|
|
73
|
+
linkFrameworkDependencies(appDir, frameworkRoot, cwd)
|
|
72
74
|
|
|
73
75
|
// ── 2. package.json interno (type: module para que Vite resuelva bien) ──
|
|
74
76
|
writeGeneratedFile(
|
|
@@ -539,18 +541,18 @@ function writeGeneratedFile(path, contents) {
|
|
|
539
541
|
}
|
|
540
542
|
|
|
541
543
|
/**
|
|
542
|
-
* Crea
|
|
543
|
-
*
|
|
544
|
-
*
|
|
544
|
+
* Crea `.ecomiq/node_modules` con symlinks a las dependencias del framework.
|
|
545
|
+
*
|
|
546
|
+
* Workerd (Cloudflare Vite plugin) resuelve desde la raíz `.ecomiq`, así que
|
|
547
|
+
* no basta con el hoisting del proyecto: hay que materializar los packages
|
|
548
|
+
* ahí. En installs npm/pnpm/yarn las deps suelen vivir en el node_modules del
|
|
549
|
+
* proyecto (o en el store de pnpm), no bajo `@ecomiq/storefront/node_modules`.
|
|
545
550
|
*/
|
|
546
|
-
function linkFrameworkDependencies(appDir, frameworkRoot) {
|
|
547
|
-
const source = join(frameworkRoot, 'node_modules')
|
|
551
|
+
function linkFrameworkDependencies(appDir, frameworkRoot, projectRoot = dirname(appDir)) {
|
|
548
552
|
const target = join(appDir, 'node_modules')
|
|
549
553
|
mkdirSync(target, { recursive: true })
|
|
550
554
|
|
|
551
|
-
// el framework mismo, para imports '@ecomiq/storefront/...'
|
|
552
555
|
linkInto(join(target, '@ecomiq'), 'storefront', frameworkRoot)
|
|
553
|
-
// limpia el nombre legacy del paquete renombrado
|
|
554
556
|
const legacyFramework = join(target, '@ecomiq', 'framework')
|
|
555
557
|
try {
|
|
556
558
|
if (lstatSync(legacyFramework).isSymbolicLink()) unlinkSync(legacyFramework)
|
|
@@ -558,12 +560,60 @@ function linkFrameworkDependencies(appDir, frameworkRoot) {
|
|
|
558
560
|
/* no existía */
|
|
559
561
|
}
|
|
560
562
|
|
|
561
|
-
|
|
563
|
+
const resolveFrom = [
|
|
564
|
+
join(frameworkRoot, 'package.json'),
|
|
565
|
+
join(projectRoot, 'package.json'),
|
|
566
|
+
join(frameworkRoot, 'src', 'codegen.mjs'),
|
|
567
|
+
]
|
|
568
|
+
|
|
569
|
+
const manifestPath = join(frameworkRoot, 'package.json')
|
|
570
|
+
let depNames = []
|
|
571
|
+
try {
|
|
572
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
573
|
+
depNames = [
|
|
574
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
575
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
576
|
+
]
|
|
577
|
+
} catch {
|
|
578
|
+
depNames = []
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Transitive crítico que Vite/TanStack piden explícitamente en SSR.
|
|
582
|
+
for (const extra of ['@tanstack/react-store', '@tanstack/history', '@tanstack/router-core']) {
|
|
583
|
+
if (!depNames.includes(extra)) depNames.push(extra)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
for (const name of depNames) {
|
|
587
|
+
const pkgDir = resolvePackageDir(name, resolveFrom)
|
|
588
|
+
if (!pkgDir) continue
|
|
589
|
+
linkPackage(target, name, pkgDir)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Fallback: mirror de node_modules locales (monorepo / nested installs).
|
|
593
|
+
for (const source of [join(frameworkRoot, 'node_modules'), join(projectRoot, 'node_modules')]) {
|
|
594
|
+
linkNodeModulesTree(source, target)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function linkPackage(targetRoot, packageName, packageDir) {
|
|
599
|
+
if (packageName.startsWith('@')) {
|
|
600
|
+
const [scope, name] = packageName.split('/')
|
|
601
|
+
if (!scope || !name) return
|
|
602
|
+
linkInto(join(targetRoot, scope), name, packageDir)
|
|
603
|
+
return
|
|
604
|
+
}
|
|
605
|
+
linkInto(targetRoot, packageName, packageDir)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function linkNodeModulesTree(source, target) {
|
|
609
|
+
if (!existsSync(source)) return
|
|
562
610
|
for (const entry of readdirSync(source)) {
|
|
563
611
|
if (entry === '.bin' || entry.startsWith('.')) continue
|
|
564
612
|
if (entry.startsWith('@')) {
|
|
565
|
-
|
|
566
|
-
|
|
613
|
+
const scopeDir = join(source, entry)
|
|
614
|
+
if (!statSync(scopeDir).isDirectory()) continue
|
|
615
|
+
for (const scoped of readdirSync(scopeDir)) {
|
|
616
|
+
linkInto(join(target, entry), scoped, join(scopeDir, scoped))
|
|
567
617
|
}
|
|
568
618
|
} else {
|
|
569
619
|
linkInto(target, entry, join(source, entry))
|
|
@@ -571,14 +621,65 @@ function linkFrameworkDependencies(appDir, frameworkRoot) {
|
|
|
571
621
|
}
|
|
572
622
|
}
|
|
573
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Resuelve el directorio raíz de un package usando la resolución de Node
|
|
626
|
+
* (funciona con hoisting npm/yarn y el store de pnpm).
|
|
627
|
+
*/
|
|
628
|
+
function resolvePackageDir(packageName, fromFiles) {
|
|
629
|
+
for (const fromFile of fromFiles) {
|
|
630
|
+
if (!existsSync(fromFile)) continue
|
|
631
|
+
try {
|
|
632
|
+
const require = createRequire(fromFile)
|
|
633
|
+
try {
|
|
634
|
+
return dirname(realpathSync(require.resolve(`${packageName}/package.json`)))
|
|
635
|
+
} catch {
|
|
636
|
+
const entry = require.resolve(packageName)
|
|
637
|
+
return findPackageRoot(entry, packageName)
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
/* probar siguiente origen */
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return null
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function findPackageRoot(startPath, packageName) {
|
|
647
|
+
let dir = dirname(startPath)
|
|
648
|
+
while (true) {
|
|
649
|
+
const pkgPath = join(dir, 'package.json')
|
|
650
|
+
if (existsSync(pkgPath)) {
|
|
651
|
+
try {
|
|
652
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
653
|
+
if (pkg.name === packageName) return realpathSync(dir)
|
|
654
|
+
} catch {
|
|
655
|
+
/* ignore */
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const parent = dirname(dir)
|
|
659
|
+
if (parent === dir) return null
|
|
660
|
+
dir = parent
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
574
664
|
function linkInto(dir, name, targetPath) {
|
|
575
665
|
mkdirSync(dir, { recursive: true })
|
|
576
666
|
const linkPath = join(dir, name)
|
|
667
|
+
let desired
|
|
668
|
+
try {
|
|
669
|
+
desired = realpathSync(targetPath)
|
|
670
|
+
} catch {
|
|
671
|
+
desired = resolve(targetPath)
|
|
672
|
+
}
|
|
577
673
|
try {
|
|
578
674
|
const current = lstatSync(linkPath)
|
|
579
675
|
if (current.isSymbolicLink()) {
|
|
580
|
-
|
|
581
|
-
|
|
676
|
+
let resolved
|
|
677
|
+
try {
|
|
678
|
+
resolved = realpathSync(linkPath)
|
|
679
|
+
} catch {
|
|
680
|
+
resolved = resolve(dir, readlinkSync(linkPath))
|
|
681
|
+
}
|
|
682
|
+
if (resolved === desired) return
|
|
582
683
|
unlinkSync(linkPath)
|
|
583
684
|
} else if (current.isDirectory() || current.isFile()) {
|
|
584
685
|
return
|
|
@@ -587,7 +688,7 @@ function linkInto(dir, name, targetPath) {
|
|
|
587
688
|
/* no existe aún */
|
|
588
689
|
}
|
|
589
690
|
try {
|
|
590
|
-
symlinkSync(
|
|
691
|
+
symlinkSync(desired, linkPath, 'junction')
|
|
591
692
|
} catch {
|
|
592
693
|
/* FS sin symlinks: la resolución cae al hoisting */
|
|
593
694
|
}
|
package/src/vite-inline.mjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* los plugins se importan aquí (contexto del framework) y se pasan como
|
|
5
5
|
* instancias, así la resolución de módulos nunca depende del proyecto.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { createRequire } from 'node:module'
|
|
8
|
+
import { dirname, resolve } from 'node:path'
|
|
8
9
|
import { fileURLToPath } from 'node:url'
|
|
9
10
|
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
10
11
|
import viteReact from '@vitejs/plugin-react'
|
|
@@ -15,6 +16,8 @@ const postcssBrowserStub = fileURLToPath(
|
|
|
15
16
|
new URL('./runtime/postcss-browser-stub.mjs', import.meta.url),
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
const frameworkRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
20
|
+
|
|
18
21
|
function resolveSanitizeHtmlPostcss(source, importer) {
|
|
19
22
|
if (source !== 'postcss') return null
|
|
20
23
|
if (!importer?.includes('/sanitize-html/')) return null
|
|
@@ -50,11 +53,48 @@ function sanitizeHtmlClientBoundary() {
|
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
function resolveFrameworkPackage(name) {
|
|
57
|
+
try {
|
|
58
|
+
const require = createRequire(resolve(frameworkRoot, 'package.json'))
|
|
59
|
+
return dirname(require.resolve(`${name}/package.json`))
|
|
60
|
+
} catch {
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createViteConfig({ appDir, config, projectRoot }) {
|
|
66
|
+
const root = projectRoot ?? resolve(appDir, '..')
|
|
67
|
+
const reactStartRoot = resolveFrameworkPackage('@tanstack/react-start')
|
|
68
|
+
const allow = new Set([
|
|
69
|
+
'..',
|
|
70
|
+
root,
|
|
71
|
+
frameworkRoot,
|
|
72
|
+
resolve(appDir, 'node_modules'),
|
|
73
|
+
resolve(root, 'node_modules'),
|
|
74
|
+
])
|
|
75
|
+
if (reactStartRoot) allow.add(reactStartRoot)
|
|
76
|
+
|
|
54
77
|
return {
|
|
55
78
|
root: appDir,
|
|
56
79
|
configFile: false,
|
|
57
80
|
publicDir: resolve(appDir, '../public'),
|
|
81
|
+
resolve: {
|
|
82
|
+
dedupe: [
|
|
83
|
+
'react',
|
|
84
|
+
'react-dom',
|
|
85
|
+
'@tanstack/react-router',
|
|
86
|
+
'@tanstack/react-start',
|
|
87
|
+
'@tanstack/react-query',
|
|
88
|
+
],
|
|
89
|
+
...(reactStartRoot
|
|
90
|
+
? {
|
|
91
|
+
alias: {
|
|
92
|
+
// Asegura subpaths (`/server-entry`) cuando workerd no hereda el hoisting.
|
|
93
|
+
'@tanstack/react-start': reactStartRoot,
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
: {}),
|
|
97
|
+
},
|
|
58
98
|
plugins: [
|
|
59
99
|
tailwindcss(),
|
|
60
100
|
// Emula el runtime de Workers en dev y empaqueta para Cloudflare en build.
|
|
@@ -88,7 +128,7 @@ export function createViteConfig({ appDir, config }) {
|
|
|
88
128
|
server: {
|
|
89
129
|
fs: {
|
|
90
130
|
// pages/, lib/ y public/ del usuario viven un nivel arriba de .ecomiq
|
|
91
|
-
allow: [
|
|
131
|
+
allow: [...allow],
|
|
92
132
|
},
|
|
93
133
|
},
|
|
94
134
|
}
|