create-ereo 0.1.22 → 0.1.24

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 (2) hide show
  1. package/dist/index.js +571 -101
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,8 +2,10 @@
2
2
  // @bun
3
3
 
4
4
  // src/index.ts
5
- import { join, resolve } from "path";
5
+ import { join, resolve, dirname } from "path";
6
6
  import { mkdir } from "fs/promises";
7
+ var pkgJsonPath = join(dirname(import.meta.dir), "package.json");
8
+ var EREO_VERSION = `^${(await Bun.file(pkgJsonPath).json()).version}`;
7
9
  var defaultOptions = {
8
10
  template: "tailwind",
9
11
  typescript: true,
@@ -44,7 +46,18 @@ function parseArgs(args) {
44
46
  process.exit(0);
45
47
  }
46
48
  if (arg === "-t" || arg === "--template") {
47
- options.template = args[++i];
49
+ if (i + 1 >= args.length) {
50
+ console.error(` \x1B[31m\u2717\x1B[0m --template requires a value (minimal, default, tailwind)
51
+ `);
52
+ process.exit(1);
53
+ }
54
+ const tmpl = args[++i];
55
+ if (tmpl !== "minimal" && tmpl !== "default" && tmpl !== "tailwind") {
56
+ console.error(` \x1B[31m\u2717\x1B[0m Unknown template "${tmpl}". Valid options: minimal, default, tailwind
57
+ `);
58
+ process.exit(1);
59
+ }
60
+ options.template = tmpl;
48
61
  } else if (arg === "--no-typescript") {
49
62
  options.typescript = false;
50
63
  } else if (arg === "--no-git") {
@@ -57,6 +70,74 @@ function parseArgs(args) {
57
70
  }
58
71
  return { projectName, options };
59
72
  }
73
+ function generateDockerfile(typescript) {
74
+ const tsconfigCopy = typescript ? `COPY --from=builder --chown=ereo:ereo /app/tsconfig.json ./
75
+ ` : "";
76
+ return `# syntax=docker/dockerfile:1
77
+
78
+ # ---- Stage 1: Install production dependencies ----
79
+ FROM oven/bun:1-slim AS deps
80
+ WORKDIR /app
81
+ COPY package.json bun.lockb* ./
82
+ RUN --mount=type=cache,target=/root/.bun/install/cache \\
83
+ bun install --frozen-lockfile --production --ignore-scripts
84
+
85
+ # ---- Stage 2: Install all deps + build ----
86
+ FROM oven/bun:1-slim AS builder
87
+ WORKDIR /app
88
+ COPY package.json bun.lockb* ./
89
+ RUN --mount=type=cache,target=/root/.bun/install/cache \\
90
+ bun install --frozen-lockfile --ignore-scripts
91
+ COPY . .
92
+ RUN bun run build
93
+
94
+ # ---- Stage 3: Production image ----
95
+ FROM oven/bun:1-slim AS runner
96
+ WORKDIR /app
97
+
98
+ ENV NODE_ENV=production
99
+
100
+ # Non-root user for security
101
+ RUN groupadd --system --gid 1001 ereo && \\
102
+ useradd --system --uid 1001 --gid ereo --no-create-home ereo
103
+
104
+ # Copy only what's needed to run
105
+ COPY --from=deps --chown=ereo:ereo /app/node_modules ./node_modules
106
+ COPY --from=builder --chown=ereo:ereo /app/.ereo ./.ereo
107
+ COPY --from=builder --chown=ereo:ereo /app/package.json ./
108
+ COPY --from=builder --chown=ereo:ereo /app/ereo.config.* ./
109
+ ${tsconfigCopy}COPY --from=builder --chown=ereo:ereo /app/app ./app
110
+ COPY --from=builder --chown=ereo:ereo /app/public ./public
111
+
112
+ USER ereo
113
+
114
+ EXPOSE 3000
115
+
116
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
117
+ CMD bun -e "fetch('http://localhost:3000').then(r=>{if(!r.ok)throw 1}).catch(()=>process.exit(1))"
118
+
119
+ CMD ["bun", "run", "start"]
120
+ `;
121
+ }
122
+ function generateDockerignore() {
123
+ return `node_modules
124
+ dist
125
+ .ereo
126
+ .git
127
+ .gitignore
128
+ .env
129
+ .env.*
130
+ !.env.example
131
+ Dockerfile
132
+ *.log
133
+ *.md
134
+ .DS_Store
135
+ .vscode
136
+ .idea
137
+ coverage
138
+ *.tgz
139
+ `;
140
+ }
60
141
  async function generateMinimalProject(projectDir, projectName, typescript) {
61
142
  const ext = typescript ? "tsx" : "jsx";
62
143
  await mkdir(projectDir, { recursive: true });
@@ -72,12 +153,12 @@ async function generateMinimalProject(projectDir, projectName, typescript) {
72
153
  start: "ereo start"
73
154
  },
74
155
  dependencies: {
75
- "@ereo/core": "^0.1.7",
76
- "@ereo/router": "^0.1.7",
77
- "@ereo/server": "^0.1.7",
78
- "@ereo/client": "^0.1.7",
79
- "@ereo/data": "^0.1.7",
80
- "@ereo/cli": "^0.1.7",
156
+ "@ereo/core": EREO_VERSION,
157
+ "@ereo/router": EREO_VERSION,
158
+ "@ereo/server": EREO_VERSION,
159
+ "@ereo/client": EREO_VERSION,
160
+ "@ereo/data": EREO_VERSION,
161
+ "@ereo/cli": EREO_VERSION,
81
162
  react: "^18.2.0",
82
163
  "react-dom": "^18.2.0"
83
164
  },
@@ -107,6 +188,88 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
107
188
  <meta charSet="utf-8" />
108
189
  <meta name="viewport" content="width=device-width, initial-scale=1" />
109
190
  <title>${projectName}</title>
191
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
192
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
193
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
194
+ <style dangerouslySetInnerHTML={{ __html: \`
195
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
196
+ :root {
197
+ --brand-500: #6366f1;
198
+ --brand-600: #4f46e5;
199
+ --brand-400: #818cf8;
200
+ --purple-500: #8b5cf6;
201
+ --violet-500: #a855f7;
202
+ --bg: #ffffff;
203
+ --bg-soft: #f8fafc;
204
+ --bg-card: #ffffff;
205
+ --text: #0f172a;
206
+ --text-soft: #64748b;
207
+ --border: #e2e8f0;
208
+ --code-bg: #1e1e2e;
209
+ }
210
+ @media (prefers-color-scheme: dark) {
211
+ :root {
212
+ --bg: #0f172a;
213
+ --bg-soft: #1e293b;
214
+ --bg-card: #1e293b;
215
+ --text: #f1f5f9;
216
+ --text-soft: #94a3b8;
217
+ --border: #334155;
218
+ --code-bg: #0f172a;
219
+ }
220
+ }
221
+ body {
222
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
223
+ background: var(--bg);
224
+ color: var(--text);
225
+ line-height: 1.6;
226
+ -webkit-font-smoothing: antialiased;
227
+ }
228
+ @keyframes fadeInUp {
229
+ from { opacity: 0; transform: translateY(24px); }
230
+ to { opacity: 1; transform: translateY(0); }
231
+ }
232
+ @keyframes float {
233
+ 0%, 100% { transform: translateY(0); }
234
+ 50% { transform: translateY(-10px); }
235
+ }
236
+ @keyframes gradientShift {
237
+ 0%, 100% { background-position: 0% 50%; }
238
+ 50% { background-position: 100% 50%; }
239
+ }
240
+ .hero { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; position: relative; overflow: hidden; }
241
+ .hero::before { content: ''; position: absolute; inset: 0; background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.15) 0%, transparent 70%); pointer-events: none; }
242
+ .hero-logo { animation: float 4s ease-in-out infinite; margin-bottom: 2rem; }
243
+ .gradient-text { background: linear-gradient(135deg, var(--brand-500), var(--purple-500), var(--violet-500)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; background-size: 200% 200%; animation: gradientShift 6s ease infinite; }
244
+ .hero h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); font-weight: 800; letter-spacing: -0.03em; margin-bottom: 1rem; animation: fadeInUp 0.6s ease both; }
245
+ .hero p { font-size: 1.25rem; color: var(--text-soft); max-width: 540px; margin-bottom: 2rem; animation: fadeInUp 0.6s ease 0.15s both; }
246
+ .btn-group { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; animation: fadeInUp 0.6s ease 0.3s both; }
247
+ .cta-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.75rem; border-radius: 0.75rem; font-weight: 600; font-size: 0.95rem; text-decoration: none; transition: all 0.2s; }
248
+ .cta-btn-primary { background: var(--brand-500); color: white; }
249
+ .cta-btn-primary:hover { background: var(--brand-600); transform: translateY(-1px); box-shadow: 0 8px 24px rgba(99,102,241,0.3); }
250
+ .cta-btn-secondary { border: 2px solid var(--border); color: var(--text); background: transparent; }
251
+ .cta-btn-secondary:hover { border-color: var(--brand-500); color: var(--brand-500); }
252
+ .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; max-width: 72rem; margin: 0 auto; padding: 5rem 2rem; }
253
+ .feature-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 1rem; padding: 2rem; transition: all 0.25s; }
254
+ .feature-card:hover { border-color: var(--brand-400); transform: translateY(-4px); box-shadow: 0 12px 32px rgba(99,102,241,0.1); }
255
+ .feature-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(139,92,246,0.1)); }
256
+ .feature-card h3 { font-size: 1.15rem; font-weight: 700; margin-bottom: 0.5rem; }
257
+ .feature-card p { color: var(--text-soft); font-size: 0.925rem; }
258
+ .code-window { background: var(--code-bg); border-radius: 1rem; overflow: hidden; max-width: 540px; margin: 0 auto; text-align: left; animation: fadeInUp 0.6s ease 0.45s both; }
259
+ .code-header { display: flex; align-items: center; gap: 6px; padding: 0.875rem 1.25rem; background: rgba(255,255,255,0.05); }
260
+ .code-dot { width: 12px; height: 12px; border-radius: 50%; }
261
+ .code-dot-r { background: #ff5f57; }
262
+ .code-dot-y { background: #febc2e; }
263
+ .code-dot-g { background: #28c840; }
264
+ .code-body { padding: 1.25rem; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; color: #a5b4fc; line-height: 1.8; }
265
+ .code-body .prompt { color: #6ee7b7; }
266
+ .quickstart-section { background: var(--bg-soft); padding: 5rem 2rem; text-align: center; }
267
+ .quickstart-section h2 { font-size: 2rem; font-weight: 700; margin-bottom: 1rem; }
268
+ .quickstart-section .subtitle { color: var(--text-soft); margin-bottom: 2rem; }
269
+ .site-footer { text-align: center; padding: 3rem 2rem; color: var(--text-soft); font-size: 0.875rem; border-top: 1px solid var(--border); }
270
+ .site-footer a { color: var(--brand-500); text-decoration: none; }
271
+ .site-footer a:hover { text-decoration: underline; }
272
+ \` }} />
110
273
  </head>
111
274
  <body>
112
275
  {children}
@@ -119,10 +282,102 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
119
282
  const indexPage = `
120
283
  export default function HomePage() {
121
284
  return (
122
- <main>
123
- <h1>Welcome to EreoJS!</h1>
124
- <p>Edit app/routes/index.${ext} to get started.</p>
125
- </main>
285
+ <>
286
+ {/* Hero */}
287
+ <section className="hero">
288
+ <div className="hero-logo">
289
+ <svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
290
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#logo-grad)" strokeWidth="3" fill="none" />
291
+ <path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#logo-grad)" opacity="0.15" />
292
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#logo-grad)" />
293
+ <defs>
294
+ <linearGradient id="logo-grad" x1="8" y1="8" x2="72" y2="72">
295
+ <stop stopColor="#6366f1" />
296
+ <stop offset="1" stopColor="#a855f7" />
297
+ </linearGradient>
298
+ </defs>
299
+ </svg>
300
+ </div>
301
+ <h1><span className="gradient-text">EreoJS</span></h1>
302
+ <p>A React fullstack framework built on Bun. Fast server-side rendering, file-based routing, and islands architecture.</p>
303
+ <div className="btn-group">
304
+ <a href="https://ereo.dev/docs" className="cta-btn cta-btn-primary">Get Started</a>
305
+ <a href="https://github.com/nicholasgriffintn/ereo" className="cta-btn cta-btn-secondary">
306
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
307
+ GitHub
308
+ </a>
309
+ </div>
310
+ </section>
311
+
312
+ {/* Features */}
313
+ <div className="feature-grid">
314
+ <div className="feature-card">
315
+ <div className="feature-icon">
316
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
317
+ </div>
318
+ <h3>Bun-Powered</h3>
319
+ <p>Built on Bun for blazing-fast startup, builds, and runtime performance.</p>
320
+ </div>
321
+ <div className="feature-card">
322
+ <div className="feature-icon">
323
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
324
+ </div>
325
+ <h3>File Routing</h3>
326
+ <p>Intuitive file-based routing with nested layouts and dynamic segments.</p>
327
+ </div>
328
+ <div className="feature-card">
329
+ <div className="feature-icon">
330
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
331
+ </div>
332
+ <h3>Server-Side Rendering</h3>
333
+ <p>Stream HTML from the server for fast, SEO-friendly initial page loads.</p>
334
+ </div>
335
+ <div className="feature-card">
336
+ <div className="feature-icon">
337
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></svg>
338
+ </div>
339
+ <h3>Islands Architecture</h3>
340
+ <p>Selective hydration \u2014 only interactive parts ship JavaScript to the client.</p>
341
+ </div>
342
+ <div className="feature-card">
343
+ <div className="feature-icon">
344
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
345
+ </div>
346
+ <h3>Loaders &amp; Actions</h3>
347
+ <p>Server-side data loading and form handling with simple async functions.</p>
348
+ </div>
349
+ <div className="feature-card">
350
+ <div className="feature-icon">
351
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
352
+ </div>
353
+ <h3>TypeScript First</h3>
354
+ <p>Full type safety out of the box with zero-config TypeScript support.</p>
355
+ </div>
356
+ </div>
357
+
358
+ {/* Quick Start */}
359
+ <section className="quickstart-section">
360
+ <h2>Get Started in Seconds</h2>
361
+ <p className="subtitle">One command to scaffold your project.</p>
362
+ <div className="code-window">
363
+ <div className="code-header">
364
+ <span className="code-dot code-dot-r" />
365
+ <span className="code-dot code-dot-y" />
366
+ <span className="code-dot code-dot-g" />
367
+ </div>
368
+ <div className="code-body">
369
+ <div><span className="prompt">$</span> bunx create-ereo my-app</div>
370
+ <div><span className="prompt">$</span> cd my-app</div>
371
+ <div><span className="prompt">$</span> bun run dev</div>
372
+ </div>
373
+ </div>
374
+ </section>
375
+
376
+ {/* Footer */}
377
+ <footer className="site-footer">
378
+ <p>Built with EreoJS &mdash; <a href="https://ereo.dev/docs">Docs</a> &middot; <a href="https://github.com/nicholasgriffintn/ereo">GitHub</a></p>
379
+ </footer>
380
+ </>
126
381
  );
127
382
  }
128
383
  `.trim();
@@ -151,6 +406,8 @@ dist
151
406
  .DS_Store
152
407
  .env
153
408
  .env.local`);
409
+ await Bun.write(join(projectDir, "Dockerfile"), generateDockerfile(typescript));
410
+ await Bun.write(join(projectDir, ".dockerignore"), generateDockerignore());
154
411
  }
155
412
  async function generateTailwindProject(projectDir, projectName, typescript) {
156
413
  const ext = typescript ? "tsx" : "jsx";
@@ -172,20 +429,20 @@ async function generateTailwindProject(projectDir, projectName, typescript) {
172
429
  typecheck: "tsc --noEmit"
173
430
  },
174
431
  dependencies: {
175
- "@ereo/core": "^0.1.7",
176
- "@ereo/router": "^0.1.7",
177
- "@ereo/server": "^0.1.7",
178
- "@ereo/client": "^0.1.7",
179
- "@ereo/data": "^0.1.7",
180
- "@ereo/cli": "^0.1.7",
181
- "@ereo/runtime-bun": "^0.1.7",
182
- "@ereo/plugin-tailwind": "^0.1.7",
432
+ "@ereo/core": EREO_VERSION,
433
+ "@ereo/router": EREO_VERSION,
434
+ "@ereo/server": EREO_VERSION,
435
+ "@ereo/client": EREO_VERSION,
436
+ "@ereo/data": EREO_VERSION,
437
+ "@ereo/cli": EREO_VERSION,
438
+ "@ereo/runtime-bun": EREO_VERSION,
439
+ "@ereo/plugin-tailwind": EREO_VERSION,
183
440
  react: "^18.2.0",
184
441
  "react-dom": "^18.2.0"
185
442
  },
186
443
  devDependencies: {
187
- "@ereo/testing": "^0.1.7",
188
- "@ereo/dev-inspector": "^0.1.7",
444
+ "@ereo/testing": EREO_VERSION,
445
+ "@ereo/dev-inspector": EREO_VERSION,
189
446
  ...ts ? {
190
447
  "@types/bun": "^1.1.0",
191
448
  "@types/react": "^18.2.0",
@@ -249,18 +506,48 @@ export default {
249
506
  darkMode: 'class',
250
507
  theme: {
251
508
  extend: {
509
+ fontFamily: {
510
+ sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
511
+ mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
512
+ },
252
513
  colors: {
253
514
  primary: {
254
- 50: '#eff6ff',
255
- 100: '#dbeafe',
256
- 200: '#bfdbfe',
257
- 300: '#93c5fd',
258
- 400: '#60a5fa',
259
- 500: '#3b82f6',
260
- 600: '#2563eb',
261
- 700: '#1d4ed8',
262
- 800: '#1e40af',
263
- 900: '#1e3a8a',
515
+ 50: '#eef2ff',
516
+ 100: '#e0e7ff',
517
+ 200: '#c7d2fe',
518
+ 300: '#a5b4fc',
519
+ 400: '#818cf8',
520
+ 500: '#6366f1',
521
+ 600: '#4f46e5',
522
+ 700: '#4338ca',
523
+ 800: '#3730a3',
524
+ 900: '#312e81',
525
+ 950: '#1e1b4b',
526
+ },
527
+ },
528
+ animation: {
529
+ 'fade-in': 'fadeIn 0.5s ease forwards',
530
+ 'slide-up': 'slideUp 0.6s ease forwards',
531
+ 'float': 'float 4s ease-in-out infinite',
532
+ 'gradient': 'gradientShift 6s ease infinite',
533
+ 'pulse-slow': 'pulse 3s ease-in-out infinite',
534
+ },
535
+ keyframes: {
536
+ fadeIn: {
537
+ '0%': { opacity: '0' },
538
+ '100%': { opacity: '1' },
539
+ },
540
+ slideUp: {
541
+ '0%': { opacity: '0', transform: 'translateY(20px)' },
542
+ '100%': { opacity: '1', transform: 'translateY(0)' },
543
+ },
544
+ float: {
545
+ '0%, 100%': { transform: 'translateY(0)' },
546
+ '50%': { transform: 'translateY(-10px)' },
547
+ },
548
+ gradientShift: {
549
+ '0%, 100%': { backgroundPosition: '0% 50%' },
550
+ '50%': { backgroundPosition: '100% 50%' },
264
551
  },
265
552
  },
266
553
  },
@@ -270,22 +557,24 @@ export default {
270
557
  `.trim();
271
558
  await Bun.write(join(projectDir, "tailwind.config.js"), tailwindConfig);
272
559
  const styles = `
560
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
561
+
273
562
  @tailwind base;
274
563
  @tailwind components;
275
564
  @tailwind utilities;
276
565
 
277
566
  @layer base {
278
567
  body {
279
- @apply antialiased;
568
+ @apply antialiased font-sans;
280
569
  }
281
570
  }
282
571
 
283
572
  @layer components {
284
573
  .btn {
285
- @apply px-4 py-2 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2;
574
+ @apply px-4 py-2 rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
286
575
  }
287
576
  .btn-primary {
288
- @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
577
+ @apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 hover:-translate-y-0.5 hover:shadow-lg;
289
578
  }
290
579
  .btn-secondary {
291
580
  @apply bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600;
@@ -296,6 +585,33 @@ export default {
296
585
  .card {
297
586
  @apply bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6;
298
587
  }
588
+ .gradient-text {
589
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-primary-500 via-purple-500 to-violet-500;
590
+ background-size: 200% 200%;
591
+ animation: gradientShift 6s ease infinite;
592
+ }
593
+ .code-window {
594
+ @apply rounded-xl overflow-hidden;
595
+ background: #1e1e2e;
596
+ }
597
+ .code-window-header {
598
+ @apply flex items-center gap-1.5 px-4 py-3;
599
+ background: rgba(255, 255, 255, 0.05);
600
+ }
601
+ .code-window-dot {
602
+ @apply w-3 h-3 rounded-full;
603
+ }
604
+ .glow {
605
+ box-shadow: 0 0 40px rgba(99, 102, 241, 0.15), 0 0 80px rgba(99, 102, 241, 0.05);
606
+ }
607
+ }
608
+
609
+ @layer utilities {
610
+ .delay-100 { animation-delay: 100ms; }
611
+ .delay-200 { animation-delay: 200ms; }
612
+ .delay-300 { animation-delay: 300ms; }
613
+ .delay-400 { animation-delay: 400ms; }
614
+ .delay-500 { animation-delay: 500ms; }
299
615
  }
300
616
  `.trim();
301
617
  await Bun.write(join(projectDir, "app/styles.css"), styles);
@@ -495,8 +811,17 @@ export function Navigation() {
495
811
  <div className="max-w-6xl mx-auto px-4">
496
812
  <div className="flex items-center justify-between h-16">
497
813
  {/* Logo */}
498
- <a href="/" className="flex items-center space-x-2">
499
- <span className="text-2xl">\u2B21</span>
814
+ <a href="/" className="flex items-center space-x-2 group">
815
+ <svg width="28" height="28" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg" className="transition-transform group-hover:scale-110">
816
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#nav-grad)" strokeWidth="3" fill="none" />
817
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#nav-grad)" />
818
+ <defs>
819
+ <linearGradient id="nav-grad" x1="8" y1="8" x2="72" y2="72">
820
+ <stop stopColor="#6366f1" />
821
+ <stop offset="1" stopColor="#a855f7" />
822
+ </linearGradient>
823
+ </defs>
824
+ </svg>
500
825
  <span className="font-bold text-xl">EreoJS</span>
501
826
  </a>
502
827
 
@@ -671,6 +996,9 @@ export default function RootLayout({ children }${ts ? ": RootLayoutProps" : ""})
671
996
  <meta name="viewport" content="width=device-width, initial-scale=1" />
672
997
  <meta name="description" content="A modern web application built with EreoJS" />
673
998
  <title>${projectName}</title>
999
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
1000
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
1001
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
674
1002
  <link rel="stylesheet" href="/app/styles.css" />
675
1003
  </head>
676
1004
  <body className="min-h-screen flex flex-col bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
@@ -726,100 +1054,210 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
726
1054
  const { featuredPost, stats } = loaderData;
727
1055
 
728
1056
  return (
729
- <div className="min-h-screen">
730
- {/* Hero Section */}
731
- <section className="py-20 px-4 bg-gradient-to-br from-primary-500 to-purple-600 text-white">
732
- <div className="max-w-4xl mx-auto text-center">
733
- <h1 className="text-5xl md:text-6xl font-bold mb-6">
734
- Welcome to EreoJS
1057
+ <div>
1058
+ {/* Hero */}
1059
+ <section className="relative min-h-[90vh] flex flex-col items-center justify-center text-center px-4 overflow-hidden">
1060
+ {/* Background */}
1061
+ <div className="absolute inset-0 bg-gradient-to-b from-primary-50/50 via-transparent to-transparent dark:from-primary-950/30 dark:via-transparent dark:to-transparent" />
1062
+ <div className="absolute inset-0" style={{ background: 'radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.12) 0%, transparent 60%)' }} />
1063
+
1064
+ <div className="relative z-10 max-w-4xl mx-auto">
1065
+ {/* Logo */}
1066
+ <div className="animate-float mb-8 opacity-0 animate-fade-in">
1067
+ <svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
1068
+ <path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#hero-grad)" strokeWidth="3" fill="none" />
1069
+ <path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#hero-grad)" opacity="0.15" />
1070
+ <path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#hero-grad)" />
1071
+ <defs>
1072
+ <linearGradient id="hero-grad" x1="8" y1="8" x2="72" y2="72">
1073
+ <stop stopColor="#6366f1" />
1074
+ <stop offset="1" stopColor="#a855f7" />
1075
+ </linearGradient>
1076
+ </defs>
1077
+ </svg>
1078
+ </div>
1079
+
1080
+ {/* Version Badge */}
1081
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 text-sm font-medium mb-6 opacity-0 animate-slide-up">
1082
+ <span className="w-2 h-2 rounded-full bg-green-500 animate-pulse-slow" />
1083
+ v${EREO_VERSION.slice(1)}
1084
+ </div>
1085
+
1086
+ <h1 className="text-5xl sm:text-6xl md:text-7xl font-extrabold tracking-tight mb-6 opacity-0 animate-slide-up delay-100">
1087
+ Build Faster with <span className="gradient-text">EreoJS</span>
735
1088
  </h1>
736
- <p className="text-xl md:text-2xl mb-8 text-primary-100">
737
- A React fullstack framework built on Bun.
738
- <br />
739
- Fast, simple, and powerful.
1089
+
1090
+ <p className="text-lg sm:text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto mb-10 opacity-0 animate-slide-up delay-200">
1091
+ A React fullstack framework built on Bun. Server-side rendering, file-based routing, and islands architecture out of the box.
740
1092
  </p>
741
- <div className="flex flex-wrap gap-4 justify-center">
742
- <a href="/blog" className="btn bg-white text-primary-600 hover:bg-primary-50">
743
- Read the Blog
1093
+
1094
+ {/* Terminal Preview */}
1095
+ <div className="code-window glow max-w-md mx-auto text-left opacity-0 animate-slide-up delay-300">
1096
+ <div className="code-window-header">
1097
+ <span className="code-window-dot bg-red-500" />
1098
+ <span className="code-window-dot bg-yellow-500" />
1099
+ <span className="code-window-dot bg-green-500" />
1100
+ </div>
1101
+ <div className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed">
1102
+ <div><span className="text-emerald-400">$</span> bunx create-ereo my-app</div>
1103
+ <div><span className="text-emerald-400">$</span> cd my-app</div>
1104
+ <div><span className="text-emerald-400">$</span> bun run dev</div>
1105
+ </div>
1106
+ </div>
1107
+
1108
+ {/* CTA Buttons */}
1109
+ <div className="flex flex-wrap gap-4 justify-center mt-10 opacity-0 animate-slide-up delay-400">
1110
+ <a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">
1111
+ Get Started
744
1112
  </a>
745
- <a
746
- href="https://github.com/ereo-js/ereo"
747
- target="_blank"
748
- rel="noopener"
749
- className="btn border-2 border-white text-white hover:bg-white/10"
750
- >
751
- View on GitHub
1113
+ <a href="/blog" className="btn btn-secondary text-base px-6 py-3">
1114
+ Read the Blog
752
1115
  </a>
753
1116
  </div>
754
1117
  </div>
755
1118
  </section>
756
1119
 
757
- {/* Features Section */}
758
- <section className="py-16 px-4">
1120
+ {/* Features */}
1121
+ <section className="py-20 px-4">
759
1122
  <div className="max-w-6xl mx-auto">
760
- <h2 className="text-3xl font-bold text-center mb-12">Why EreoJS?</h2>
761
- <div className="grid md:grid-cols-3 gap-8">
762
- <div className="card text-center">
763
- <div className="text-4xl mb-4">\u26A1</div>
764
- <h3 className="text-xl font-bold mb-2">Blazing Fast</h3>
765
- <p className="text-gray-600 dark:text-gray-400">
766
- Built on Bun for exceptional performance. Server-side rendering with streaming support.
767
- </p>
768
- </div>
769
- <div className="card text-center">
770
- <div className="text-4xl mb-4">\uD83C\uDFAF</div>
771
- <h3 className="text-xl font-bold mb-2">Simple Data Loading</h3>
772
- <p className="text-gray-600 dark:text-gray-400">
773
- One pattern for data fetching. Loaders and actions make it easy to build dynamic apps.
774
- </p>
1123
+ <div className="text-center mb-16">
1124
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Everything You Need</h2>
1125
+ <p className="text-gray-600 dark:text-gray-400 text-lg max-w-2xl mx-auto">
1126
+ EreoJS combines the best of React with Bun's performance to deliver a complete fullstack framework.
1127
+ </p>
1128
+ </div>
1129
+ <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
1130
+ {[
1131
+ { icon: 'bolt', title: 'Bun-Powered', desc: 'Lightning-fast startup, builds, and runtime. 10x faster than Node.js for common operations.' },
1132
+ { icon: 'folder', title: 'File Routing', desc: 'Intuitive file-based routing with nested layouts, dynamic segments, and automatic code splitting.' },
1133
+ { icon: 'server', title: 'Server-Side Rendering', desc: 'Stream HTML from the server for fast initial loads with full SEO support.' },
1134
+ { icon: 'island', title: 'Islands Architecture', desc: 'Selective hydration means only interactive parts ship JavaScript to the client.' },
1135
+ { icon: 'data', title: 'Loaders & Actions', desc: 'Simple async functions for server-side data loading and form handling.' },
1136
+ { icon: 'ts', title: 'TypeScript First', desc: 'Full type safety with zero-config TypeScript. Types flow from server to client.' },
1137
+ ].map((feature) => (
1138
+ <div key={feature.title} className="group card hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border border-transparent hover:border-primary-200 dark:hover:border-primary-800">
1139
+ <div className="w-12 h-12 rounded-xl flex items-center justify-center mb-4 bg-gradient-to-br from-primary-100 to-purple-100 dark:from-primary-900/40 dark:to-purple-900/40 group-hover:from-primary-200 group-hover:to-purple-200 dark:group-hover:from-primary-900/60 dark:group-hover:to-purple-900/60 transition-colors">
1140
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1141
+ {feature.icon === 'bolt' && <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />}
1142
+ {feature.icon === 'folder' && <path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z" />}
1143
+ {feature.icon === 'server' && <><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></>}
1144
+ {feature.icon === 'island' && <><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></>}
1145
+ {feature.icon === 'data' && <><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>}
1146
+ {feature.icon === 'ts' && <><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></>}
1147
+ </svg>
1148
+ </div>
1149
+ <h3 className="text-lg font-bold mb-2">{feature.title}</h3>
1150
+ <p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">{feature.desc}</p>
1151
+ </div>
1152
+ ))}
1153
+ </div>
1154
+ </div>
1155
+ </section>
1156
+
1157
+ {/* Code Showcase */}
1158
+ <section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
1159
+ <div className="max-w-6xl mx-auto">
1160
+ <div className="text-center mb-16">
1161
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Simple, Powerful APIs</h2>
1162
+ <p className="text-gray-600 dark:text-gray-400 text-lg">Loaders fetch data. Actions handle mutations. It's that simple.</p>
1163
+ </div>
1164
+ <div className="grid md:grid-cols-2 gap-6">
1165
+ <div className="code-window">
1166
+ <div className="code-window-header">
1167
+ <span className="code-window-dot bg-red-500" />
1168
+ <span className="code-window-dot bg-yellow-500" />
1169
+ <span className="code-window-dot bg-green-500" />
1170
+ <span className="ml-3 text-xs text-gray-500 font-mono">routes/users.tsx</span>
1171
+ </div>
1172
+ <pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
1173
+ {\`// Data runs on the server
1174
+ export async function loader() {
1175
+ const users = await db.user.findMany();
1176
+ return { users };
1177
+ }
1178
+
1179
+ // Component renders on server + client
1180
+ export default function Users({ loaderData }) {
1181
+ return (
1182
+ <ul>
1183
+ {loaderData.users.map(user => (
1184
+ <li key={user.id}>{user.name}</li>
1185
+ ))}
1186
+ </ul>
1187
+ );
1188
+ }\`}
1189
+ </pre>
775
1190
  </div>
776
- <div className="card text-center">
777
- <div className="text-4xl mb-4">\uD83C\uDFDD\uFE0F</div>
778
- <h3 className="text-xl font-bold mb-2">Islands Architecture</h3>
779
- <p className="text-gray-600 dark:text-gray-400">
780
- Selective hydration means smaller bundles and faster interactivity where it matters.
781
- </p>
1191
+ <div className="code-window">
1192
+ <div className="code-window-header">
1193
+ <span className="code-window-dot bg-red-500" />
1194
+ <span className="code-window-dot bg-yellow-500" />
1195
+ <span className="code-window-dot bg-green-500" />
1196
+ <span className="ml-3 text-xs text-gray-500 font-mono">routes/contact.tsx</span>
1197
+ </div>
1198
+ <pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
1199
+ {\`// Actions handle form submissions
1200
+ export async function action({ request }) {
1201
+ const form = await request.formData();
1202
+ await db.message.create({
1203
+ data: {
1204
+ name: form.get('name'),
1205
+ email: form.get('email'),
1206
+ body: form.get('message'),
1207
+ }
1208
+ });
1209
+ return { success: true };
1210
+ }\`}
1211
+ </pre>
782
1212
  </div>
783
1213
  </div>
784
1214
  </div>
785
1215
  </section>
786
1216
 
787
- {/* Interactive Demo Section */}
788
- <section className="py-16 px-4 bg-gray-50 dark:bg-gray-800">
789
- <div className="max-w-4xl mx-auto text-center">
790
- <h2 className="text-3xl font-bold mb-4">Interactive Islands</h2>
791
- <p className="text-gray-600 dark:text-gray-400 mb-8">
792
- This counter component is an "island" - only this part of the page is hydrated with JavaScript.
793
- </p>
794
- <div className="flex justify-center">
795
- <Counter initialCount={0} />
1217
+ {/* Interactive Demo */}
1218
+ <section className="py-20 px-4">
1219
+ <div className="max-w-4xl mx-auto">
1220
+ <div className="card text-center border border-gray-200 dark:border-gray-700">
1221
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 text-xs font-medium mb-6">
1222
+ Interactive Island
1223
+ </div>
1224
+ <h2 className="text-3xl font-bold mb-3">Islands Architecture</h2>
1225
+ <p className="text-gray-600 dark:text-gray-400 mb-8 max-w-lg mx-auto">
1226
+ This counter is an island \u2014 it's the only part of this page that ships JavaScript. The rest is pure HTML from the server.
1227
+ </p>
1228
+ <div className="flex justify-center">
1229
+ <Counter initialCount={0} />
1230
+ </div>
796
1231
  </div>
797
1232
  </div>
798
1233
  </section>
799
1234
 
800
- {/* Server Data Section */}
801
- <section className="py-16 px-4">
1235
+ {/* Server Data */}
1236
+ <section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
802
1237
  <div className="max-w-4xl mx-auto">
803
- <div className="card">
804
- <h2 className="text-2xl font-bold mb-6">Server-Side Data</h2>
1238
+ <div className="card border border-gray-200 dark:border-gray-700">
1239
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-700 dark:text-emerald-300 text-xs font-medium mb-6">
1240
+ Server Loader Data
1241
+ </div>
1242
+ <h2 className="text-2xl font-bold mb-2">Loaded at Build Time</h2>
805
1243
  <p className="text-gray-600 dark:text-gray-400 mb-6">
806
- This data was loaded on the server using a loader function:
1244
+ This data was fetched on the server via a loader function \u2014 zero client-side fetching.
807
1245
  </p>
808
- <div className="grid md:grid-cols-2 gap-6">
809
- <div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
1246
+ <div className="grid sm:grid-cols-2 gap-4 mb-6">
1247
+ <div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
810
1248
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Blog Posts</div>
811
- <div className="text-3xl font-bold">{stats.posts}</div>
1249
+ <div className="text-4xl font-extrabold">{stats.posts}</div>
812
1250
  </div>
813
- <div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
1251
+ <div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
814
1252
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Rendered At</div>
815
- <div className="text-3xl font-bold">{stats.serverTime}</div>
1253
+ <div className="text-4xl font-extrabold font-mono">{stats.serverTime}</div>
816
1254
  </div>
817
1255
  </div>
818
1256
  {featuredPost && (
819
- <div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
1257
+ <div className="pt-6 border-t border-gray-200 dark:border-gray-700">
820
1258
  <div className="text-sm text-gray-500 dark:text-gray-400 mb-2">Featured Post</div>
821
1259
  <h3 className="text-xl font-bold mb-2">
822
- <a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600">
1260
+ <a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600 transition-colors">
823
1261
  {featuredPost.title}
824
1262
  </a>
825
1263
  </h3>
@@ -829,6 +1267,26 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
829
1267
  </div>
830
1268
  </div>
831
1269
  </section>
1270
+
1271
+ {/* CTA Footer */}
1272
+ <section className="py-24 px-4 text-center">
1273
+ <h2 className="text-3xl sm:text-4xl font-bold mb-4">Ready to Build?</h2>
1274
+ <p className="text-gray-600 dark:text-gray-400 text-lg mb-8">Get started with a single command.</p>
1275
+ <div className="code-window glow max-w-sm mx-auto mb-8">
1276
+ <div className="code-window-header">
1277
+ <span className="code-window-dot bg-red-500" />
1278
+ <span className="code-window-dot bg-yellow-500" />
1279
+ <span className="code-window-dot bg-green-500" />
1280
+ </div>
1281
+ <div className="px-5 py-3 font-mono text-sm text-primary-300">
1282
+ <span className="text-emerald-400">$</span> bunx create-ereo my-app
1283
+ </div>
1284
+ </div>
1285
+ <div className="flex flex-wrap gap-4 justify-center">
1286
+ <a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">Documentation</a>
1287
+ <a href="https://github.com/ereo-js/ereo" target="_blank" rel="noopener" className="btn btn-secondary text-base px-6 py-3">GitHub</a>
1288
+ </div>
1289
+ </section>
832
1290
  </div>
833
1291
  );
834
1292
  }
@@ -1311,6 +1769,8 @@ dist
1311
1769
  .env
1312
1770
  .env.local
1313
1771
  .env.*.local`);
1772
+ await Bun.write(join(projectDir, "Dockerfile"), generateDockerfile(typescript));
1773
+ await Bun.write(join(projectDir, ".dockerignore"), generateDockerignore());
1314
1774
  await Bun.write(join(projectDir, ".env.example"), `# Environment Variables
1315
1775
  # Copy this file to .env and fill in your values
1316
1776
 
@@ -1438,8 +1898,18 @@ async function main() {
1438
1898
  printHelp();
1439
1899
  process.exit(1);
1440
1900
  }
1901
+ if (/[<>:"|?*]/.test(projectName) || projectName.startsWith(".")) {
1902
+ console.error(` \x1B[31m\u2717\x1B[0m Invalid project name. Avoid special characters and leading dots.
1903
+ `);
1904
+ process.exit(1);
1905
+ }
1441
1906
  const finalOptions = { ...defaultOptions, ...options };
1442
1907
  const projectDir = resolve(process.cwd(), projectName);
1908
+ if (!projectDir.startsWith(process.cwd())) {
1909
+ console.error(` \x1B[31m\u2717\x1B[0m Invalid project name: path traversal detected.
1910
+ `);
1911
+ process.exit(1);
1912
+ }
1443
1913
  console.log(` Creating \x1B[36m${projectName}\x1B[0m...
1444
1914
  `);
1445
1915
  console.log(` Template: ${finalOptions.template}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ereo",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "license": "MIT",
5
5
  "author": "Ereo Team",
6
6
  "homepage": "https://ereo.dev",