create-ereo 0.1.21 → 0.1.23
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/dist/index.js +480 -84
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,7 +44,18 @@ function parseArgs(args) {
|
|
|
44
44
|
process.exit(0);
|
|
45
45
|
}
|
|
46
46
|
if (arg === "-t" || arg === "--template") {
|
|
47
|
-
|
|
47
|
+
if (i + 1 >= args.length) {
|
|
48
|
+
console.error(` \x1B[31m\u2717\x1B[0m --template requires a value (minimal, default, tailwind)
|
|
49
|
+
`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const tmpl = args[++i];
|
|
53
|
+
if (tmpl !== "minimal" && tmpl !== "default" && tmpl !== "tailwind") {
|
|
54
|
+
console.error(` \x1B[31m\u2717\x1B[0m Unknown template "${tmpl}". Valid options: minimal, default, tailwind
|
|
55
|
+
`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
options.template = tmpl;
|
|
48
59
|
} else if (arg === "--no-typescript") {
|
|
49
60
|
options.typescript = false;
|
|
50
61
|
} else if (arg === "--no-git") {
|
|
@@ -107,6 +118,88 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
|
|
|
107
118
|
<meta charSet="utf-8" />
|
|
108
119
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
109
120
|
<title>${projectName}</title>
|
|
121
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
122
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
|
123
|
+
<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" />
|
|
124
|
+
<style dangerouslySetInnerHTML={{ __html: \`
|
|
125
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
126
|
+
:root {
|
|
127
|
+
--brand-500: #6366f1;
|
|
128
|
+
--brand-600: #4f46e5;
|
|
129
|
+
--brand-400: #818cf8;
|
|
130
|
+
--purple-500: #8b5cf6;
|
|
131
|
+
--violet-500: #a855f7;
|
|
132
|
+
--bg: #ffffff;
|
|
133
|
+
--bg-soft: #f8fafc;
|
|
134
|
+
--bg-card: #ffffff;
|
|
135
|
+
--text: #0f172a;
|
|
136
|
+
--text-soft: #64748b;
|
|
137
|
+
--border: #e2e8f0;
|
|
138
|
+
--code-bg: #1e1e2e;
|
|
139
|
+
}
|
|
140
|
+
@media (prefers-color-scheme: dark) {
|
|
141
|
+
:root {
|
|
142
|
+
--bg: #0f172a;
|
|
143
|
+
--bg-soft: #1e293b;
|
|
144
|
+
--bg-card: #1e293b;
|
|
145
|
+
--text: #f1f5f9;
|
|
146
|
+
--text-soft: #94a3b8;
|
|
147
|
+
--border: #334155;
|
|
148
|
+
--code-bg: #0f172a;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
body {
|
|
152
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
153
|
+
background: var(--bg);
|
|
154
|
+
color: var(--text);
|
|
155
|
+
line-height: 1.6;
|
|
156
|
+
-webkit-font-smoothing: antialiased;
|
|
157
|
+
}
|
|
158
|
+
@keyframes fadeInUp {
|
|
159
|
+
from { opacity: 0; transform: translateY(24px); }
|
|
160
|
+
to { opacity: 1; transform: translateY(0); }
|
|
161
|
+
}
|
|
162
|
+
@keyframes float {
|
|
163
|
+
0%, 100% { transform: translateY(0); }
|
|
164
|
+
50% { transform: translateY(-10px); }
|
|
165
|
+
}
|
|
166
|
+
@keyframes gradientShift {
|
|
167
|
+
0%, 100% { background-position: 0% 50%; }
|
|
168
|
+
50% { background-position: 100% 50%; }
|
|
169
|
+
}
|
|
170
|
+
.hero { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; position: relative; overflow: hidden; }
|
|
171
|
+
.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; }
|
|
172
|
+
.hero-logo { animation: float 4s ease-in-out infinite; margin-bottom: 2rem; }
|
|
173
|
+
.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; }
|
|
174
|
+
.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; }
|
|
175
|
+
.hero p { font-size: 1.25rem; color: var(--text-soft); max-width: 540px; margin-bottom: 2rem; animation: fadeInUp 0.6s ease 0.15s both; }
|
|
176
|
+
.btn-group { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; animation: fadeInUp 0.6s ease 0.3s both; }
|
|
177
|
+
.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; }
|
|
178
|
+
.cta-btn-primary { background: var(--brand-500); color: white; }
|
|
179
|
+
.cta-btn-primary:hover { background: var(--brand-600); transform: translateY(-1px); box-shadow: 0 8px 24px rgba(99,102,241,0.3); }
|
|
180
|
+
.cta-btn-secondary { border: 2px solid var(--border); color: var(--text); background: transparent; }
|
|
181
|
+
.cta-btn-secondary:hover { border-color: var(--brand-500); color: var(--brand-500); }
|
|
182
|
+
.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; }
|
|
183
|
+
.feature-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 1rem; padding: 2rem; transition: all 0.25s; }
|
|
184
|
+
.feature-card:hover { border-color: var(--brand-400); transform: translateY(-4px); box-shadow: 0 12px 32px rgba(99,102,241,0.1); }
|
|
185
|
+
.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)); }
|
|
186
|
+
.feature-card h3 { font-size: 1.15rem; font-weight: 700; margin-bottom: 0.5rem; }
|
|
187
|
+
.feature-card p { color: var(--text-soft); font-size: 0.925rem; }
|
|
188
|
+
.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; }
|
|
189
|
+
.code-header { display: flex; align-items: center; gap: 6px; padding: 0.875rem 1.25rem; background: rgba(255,255,255,0.05); }
|
|
190
|
+
.code-dot { width: 12px; height: 12px; border-radius: 50%; }
|
|
191
|
+
.code-dot-r { background: #ff5f57; }
|
|
192
|
+
.code-dot-y { background: #febc2e; }
|
|
193
|
+
.code-dot-g { background: #28c840; }
|
|
194
|
+
.code-body { padding: 1.25rem; font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; color: #a5b4fc; line-height: 1.8; }
|
|
195
|
+
.code-body .prompt { color: #6ee7b7; }
|
|
196
|
+
.quickstart-section { background: var(--bg-soft); padding: 5rem 2rem; text-align: center; }
|
|
197
|
+
.quickstart-section h2 { font-size: 2rem; font-weight: 700; margin-bottom: 1rem; }
|
|
198
|
+
.quickstart-section .subtitle { color: var(--text-soft); margin-bottom: 2rem; }
|
|
199
|
+
.site-footer { text-align: center; padding: 3rem 2rem; color: var(--text-soft); font-size: 0.875rem; border-top: 1px solid var(--border); }
|
|
200
|
+
.site-footer a { color: var(--brand-500); text-decoration: none; }
|
|
201
|
+
.site-footer a:hover { text-decoration: underline; }
|
|
202
|
+
\` }} />
|
|
110
203
|
</head>
|
|
111
204
|
<body>
|
|
112
205
|
{children}
|
|
@@ -119,10 +212,102 @@ export default function RootLayout({ children }${typescript ? ": { children: Rea
|
|
|
119
212
|
const indexPage = `
|
|
120
213
|
export default function HomePage() {
|
|
121
214
|
return (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
|
|
215
|
+
<>
|
|
216
|
+
{/* Hero */}
|
|
217
|
+
<section className="hero">
|
|
218
|
+
<div className="hero-logo">
|
|
219
|
+
<svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
220
|
+
<path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#logo-grad)" strokeWidth="3" fill="none" />
|
|
221
|
+
<path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#logo-grad)" opacity="0.15" />
|
|
222
|
+
<path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#logo-grad)" />
|
|
223
|
+
<defs>
|
|
224
|
+
<linearGradient id="logo-grad" x1="8" y1="8" x2="72" y2="72">
|
|
225
|
+
<stop stopColor="#6366f1" />
|
|
226
|
+
<stop offset="1" stopColor="#a855f7" />
|
|
227
|
+
</linearGradient>
|
|
228
|
+
</defs>
|
|
229
|
+
</svg>
|
|
230
|
+
</div>
|
|
231
|
+
<h1><span className="gradient-text">EreoJS</span></h1>
|
|
232
|
+
<p>A React fullstack framework built on Bun. Fast server-side rendering, file-based routing, and islands architecture.</p>
|
|
233
|
+
<div className="btn-group">
|
|
234
|
+
<a href="https://ereo.dev/docs" className="cta-btn cta-btn-primary">Get Started</a>
|
|
235
|
+
<a href="https://github.com/nicholasgriffintn/ereo" className="cta-btn cta-btn-secondary">
|
|
236
|
+
<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>
|
|
237
|
+
GitHub
|
|
238
|
+
</a>
|
|
239
|
+
</div>
|
|
240
|
+
</section>
|
|
241
|
+
|
|
242
|
+
{/* Features */}
|
|
243
|
+
<div className="feature-grid">
|
|
244
|
+
<div className="feature-card">
|
|
245
|
+
<div className="feature-icon">
|
|
246
|
+
<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>
|
|
247
|
+
</div>
|
|
248
|
+
<h3>Bun-Powered</h3>
|
|
249
|
+
<p>Built on Bun for blazing-fast startup, builds, and runtime performance.</p>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="feature-card">
|
|
252
|
+
<div className="feature-icon">
|
|
253
|
+
<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>
|
|
254
|
+
</div>
|
|
255
|
+
<h3>File Routing</h3>
|
|
256
|
+
<p>Intuitive file-based routing with nested layouts and dynamic segments.</p>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="feature-card">
|
|
259
|
+
<div className="feature-icon">
|
|
260
|
+
<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>
|
|
261
|
+
</div>
|
|
262
|
+
<h3>Server-Side Rendering</h3>
|
|
263
|
+
<p>Stream HTML from the server for fast, SEO-friendly initial page loads.</p>
|
|
264
|
+
</div>
|
|
265
|
+
<div className="feature-card">
|
|
266
|
+
<div className="feature-icon">
|
|
267
|
+
<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>
|
|
268
|
+
</div>
|
|
269
|
+
<h3>Islands Architecture</h3>
|
|
270
|
+
<p>Selective hydration \u2014 only interactive parts ship JavaScript to the client.</p>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="feature-card">
|
|
273
|
+
<div className="feature-icon">
|
|
274
|
+
<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>
|
|
275
|
+
</div>
|
|
276
|
+
<h3>Loaders & Actions</h3>
|
|
277
|
+
<p>Server-side data loading and form handling with simple async functions.</p>
|
|
278
|
+
</div>
|
|
279
|
+
<div className="feature-card">
|
|
280
|
+
<div className="feature-icon">
|
|
281
|
+
<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>
|
|
282
|
+
</div>
|
|
283
|
+
<h3>TypeScript First</h3>
|
|
284
|
+
<p>Full type safety out of the box with zero-config TypeScript support.</p>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
{/* Quick Start */}
|
|
289
|
+
<section className="quickstart-section">
|
|
290
|
+
<h2>Get Started in Seconds</h2>
|
|
291
|
+
<p className="subtitle">One command to scaffold your project.</p>
|
|
292
|
+
<div className="code-window">
|
|
293
|
+
<div className="code-header">
|
|
294
|
+
<span className="code-dot code-dot-r" />
|
|
295
|
+
<span className="code-dot code-dot-y" />
|
|
296
|
+
<span className="code-dot code-dot-g" />
|
|
297
|
+
</div>
|
|
298
|
+
<div className="code-body">
|
|
299
|
+
<div><span className="prompt">$</span> bunx create-ereo my-app</div>
|
|
300
|
+
<div><span className="prompt">$</span> cd my-app</div>
|
|
301
|
+
<div><span className="prompt">$</span> bun run dev</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</section>
|
|
305
|
+
|
|
306
|
+
{/* Footer */}
|
|
307
|
+
<footer className="site-footer">
|
|
308
|
+
<p>Built with EreoJS — <a href="https://ereo.dev/docs">Docs</a> · <a href="https://github.com/nicholasgriffintn/ereo">GitHub</a></p>
|
|
309
|
+
</footer>
|
|
310
|
+
</>
|
|
126
311
|
);
|
|
127
312
|
}
|
|
128
313
|
`.trim();
|
|
@@ -249,18 +434,48 @@ export default {
|
|
|
249
434
|
darkMode: 'class',
|
|
250
435
|
theme: {
|
|
251
436
|
extend: {
|
|
437
|
+
fontFamily: {
|
|
438
|
+
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
|
439
|
+
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
|
440
|
+
},
|
|
252
441
|
colors: {
|
|
253
442
|
primary: {
|
|
254
|
-
50: '#
|
|
255
|
-
100: '#
|
|
256
|
-
200: '#
|
|
257
|
-
300: '#
|
|
258
|
-
400: '#
|
|
259
|
-
500: '#
|
|
260
|
-
600: '#
|
|
261
|
-
700: '#
|
|
262
|
-
800: '#
|
|
263
|
-
900: '#
|
|
443
|
+
50: '#eef2ff',
|
|
444
|
+
100: '#e0e7ff',
|
|
445
|
+
200: '#c7d2fe',
|
|
446
|
+
300: '#a5b4fc',
|
|
447
|
+
400: '#818cf8',
|
|
448
|
+
500: '#6366f1',
|
|
449
|
+
600: '#4f46e5',
|
|
450
|
+
700: '#4338ca',
|
|
451
|
+
800: '#3730a3',
|
|
452
|
+
900: '#312e81',
|
|
453
|
+
950: '#1e1b4b',
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
animation: {
|
|
457
|
+
'fade-in': 'fadeIn 0.5s ease forwards',
|
|
458
|
+
'slide-up': 'slideUp 0.6s ease forwards',
|
|
459
|
+
'float': 'float 4s ease-in-out infinite',
|
|
460
|
+
'gradient': 'gradientShift 6s ease infinite',
|
|
461
|
+
'pulse-slow': 'pulse 3s ease-in-out infinite',
|
|
462
|
+
},
|
|
463
|
+
keyframes: {
|
|
464
|
+
fadeIn: {
|
|
465
|
+
'0%': { opacity: '0' },
|
|
466
|
+
'100%': { opacity: '1' },
|
|
467
|
+
},
|
|
468
|
+
slideUp: {
|
|
469
|
+
'0%': { opacity: '0', transform: 'translateY(20px)' },
|
|
470
|
+
'100%': { opacity: '1', transform: 'translateY(0)' },
|
|
471
|
+
},
|
|
472
|
+
float: {
|
|
473
|
+
'0%, 100%': { transform: 'translateY(0)' },
|
|
474
|
+
'50%': { transform: 'translateY(-10px)' },
|
|
475
|
+
},
|
|
476
|
+
gradientShift: {
|
|
477
|
+
'0%, 100%': { backgroundPosition: '0% 50%' },
|
|
478
|
+
'50%': { backgroundPosition: '100% 50%' },
|
|
264
479
|
},
|
|
265
480
|
},
|
|
266
481
|
},
|
|
@@ -270,22 +485,24 @@ export default {
|
|
|
270
485
|
`.trim();
|
|
271
486
|
await Bun.write(join(projectDir, "tailwind.config.js"), tailwindConfig);
|
|
272
487
|
const styles = `
|
|
488
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
489
|
+
|
|
273
490
|
@tailwind base;
|
|
274
491
|
@tailwind components;
|
|
275
492
|
@tailwind utilities;
|
|
276
493
|
|
|
277
494
|
@layer base {
|
|
278
495
|
body {
|
|
279
|
-
@apply antialiased;
|
|
496
|
+
@apply antialiased font-sans;
|
|
280
497
|
}
|
|
281
498
|
}
|
|
282
499
|
|
|
283
500
|
@layer components {
|
|
284
501
|
.btn {
|
|
285
|
-
@apply px-4 py-2 rounded-lg font-medium transition-
|
|
502
|
+
@apply px-4 py-2 rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
|
|
286
503
|
}
|
|
287
504
|
.btn-primary {
|
|
288
|
-
@apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500;
|
|
505
|
+
@apply bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 hover:-translate-y-0.5 hover:shadow-lg;
|
|
289
506
|
}
|
|
290
507
|
.btn-secondary {
|
|
291
508
|
@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 +513,33 @@ export default {
|
|
|
296
513
|
.card {
|
|
297
514
|
@apply bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6;
|
|
298
515
|
}
|
|
516
|
+
.gradient-text {
|
|
517
|
+
@apply bg-clip-text text-transparent bg-gradient-to-r from-primary-500 via-purple-500 to-violet-500;
|
|
518
|
+
background-size: 200% 200%;
|
|
519
|
+
animation: gradientShift 6s ease infinite;
|
|
520
|
+
}
|
|
521
|
+
.code-window {
|
|
522
|
+
@apply rounded-xl overflow-hidden;
|
|
523
|
+
background: #1e1e2e;
|
|
524
|
+
}
|
|
525
|
+
.code-window-header {
|
|
526
|
+
@apply flex items-center gap-1.5 px-4 py-3;
|
|
527
|
+
background: rgba(255, 255, 255, 0.05);
|
|
528
|
+
}
|
|
529
|
+
.code-window-dot {
|
|
530
|
+
@apply w-3 h-3 rounded-full;
|
|
531
|
+
}
|
|
532
|
+
.glow {
|
|
533
|
+
box-shadow: 0 0 40px rgba(99, 102, 241, 0.15), 0 0 80px rgba(99, 102, 241, 0.05);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
@layer utilities {
|
|
538
|
+
.delay-100 { animation-delay: 100ms; }
|
|
539
|
+
.delay-200 { animation-delay: 200ms; }
|
|
540
|
+
.delay-300 { animation-delay: 300ms; }
|
|
541
|
+
.delay-400 { animation-delay: 400ms; }
|
|
542
|
+
.delay-500 { animation-delay: 500ms; }
|
|
299
543
|
}
|
|
300
544
|
`.trim();
|
|
301
545
|
await Bun.write(join(projectDir, "app/styles.css"), styles);
|
|
@@ -495,8 +739,17 @@ export function Navigation() {
|
|
|
495
739
|
<div className="max-w-6xl mx-auto px-4">
|
|
496
740
|
<div className="flex items-center justify-between h-16">
|
|
497
741
|
{/* Logo */}
|
|
498
|
-
<a href="/" className="flex items-center space-x-2">
|
|
499
|
-
<
|
|
742
|
+
<a href="/" className="flex items-center space-x-2 group">
|
|
743
|
+
<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">
|
|
744
|
+
<path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#nav-grad)" strokeWidth="3" fill="none" />
|
|
745
|
+
<path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#nav-grad)" />
|
|
746
|
+
<defs>
|
|
747
|
+
<linearGradient id="nav-grad" x1="8" y1="8" x2="72" y2="72">
|
|
748
|
+
<stop stopColor="#6366f1" />
|
|
749
|
+
<stop offset="1" stopColor="#a855f7" />
|
|
750
|
+
</linearGradient>
|
|
751
|
+
</defs>
|
|
752
|
+
</svg>
|
|
500
753
|
<span className="font-bold text-xl">EreoJS</span>
|
|
501
754
|
</a>
|
|
502
755
|
|
|
@@ -671,6 +924,9 @@ export default function RootLayout({ children }${ts ? ": RootLayoutProps" : ""})
|
|
|
671
924
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
672
925
|
<meta name="description" content="A modern web application built with EreoJS" />
|
|
673
926
|
<title>${projectName}</title>
|
|
927
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
928
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
|
929
|
+
<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
930
|
<link rel="stylesheet" href="/app/styles.css" />
|
|
675
931
|
</head>
|
|
676
932
|
<body className="min-h-screen flex flex-col bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
|
@@ -726,100 +982,210 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
|
|
|
726
982
|
const { featuredPost, stats } = loaderData;
|
|
727
983
|
|
|
728
984
|
return (
|
|
729
|
-
<div
|
|
730
|
-
{/* Hero
|
|
731
|
-
<section className="
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
985
|
+
<div>
|
|
986
|
+
{/* Hero */}
|
|
987
|
+
<section className="relative min-h-[90vh] flex flex-col items-center justify-center text-center px-4 overflow-hidden">
|
|
988
|
+
{/* Background */}
|
|
989
|
+
<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" />
|
|
990
|
+
<div className="absolute inset-0" style={{ background: 'radial-gradient(ellipse at 50% 0%, rgba(99,102,241,0.12) 0%, transparent 60%)' }} />
|
|
991
|
+
|
|
992
|
+
<div className="relative z-10 max-w-4xl mx-auto">
|
|
993
|
+
{/* Logo */}
|
|
994
|
+
<div className="animate-float mb-8 opacity-0 animate-fade-in">
|
|
995
|
+
<svg width="72" height="72" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
996
|
+
<path d="M40 8L72 24V56L40 72L8 56V24L40 8Z" stroke="url(#hero-grad)" strokeWidth="3" fill="none" />
|
|
997
|
+
<path d="M40 20L60 30V50L40 60L20 50V30L40 20Z" fill="url(#hero-grad)" opacity="0.15" />
|
|
998
|
+
<path d="M40 28L52 34V46L40 52L28 46V34L40 28Z" fill="url(#hero-grad)" />
|
|
999
|
+
<defs>
|
|
1000
|
+
<linearGradient id="hero-grad" x1="8" y1="8" x2="72" y2="72">
|
|
1001
|
+
<stop stopColor="#6366f1" />
|
|
1002
|
+
<stop offset="1" stopColor="#a855f7" />
|
|
1003
|
+
</linearGradient>
|
|
1004
|
+
</defs>
|
|
1005
|
+
</svg>
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
{/* Version Badge */}
|
|
1009
|
+
<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">
|
|
1010
|
+
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse-slow" />
|
|
1011
|
+
v0.1.7
|
|
1012
|
+
</div>
|
|
1013
|
+
|
|
1014
|
+
<h1 className="text-5xl sm:text-6xl md:text-7xl font-extrabold tracking-tight mb-6 opacity-0 animate-slide-up delay-100">
|
|
1015
|
+
Build Faster with <span className="gradient-text">EreoJS</span>
|
|
735
1016
|
</h1>
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
Fast, simple, and powerful.
|
|
1017
|
+
|
|
1018
|
+
<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">
|
|
1019
|
+
A React fullstack framework built on Bun. Server-side rendering, file-based routing, and islands architecture out of the box.
|
|
740
1020
|
</p>
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1021
|
+
|
|
1022
|
+
{/* Terminal Preview */}
|
|
1023
|
+
<div className="code-window glow max-w-md mx-auto text-left opacity-0 animate-slide-up delay-300">
|
|
1024
|
+
<div className="code-window-header">
|
|
1025
|
+
<span className="code-window-dot bg-red-500" />
|
|
1026
|
+
<span className="code-window-dot bg-yellow-500" />
|
|
1027
|
+
<span className="code-window-dot bg-green-500" />
|
|
1028
|
+
</div>
|
|
1029
|
+
<div className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed">
|
|
1030
|
+
<div><span className="text-emerald-400">$</span> bunx create-ereo my-app</div>
|
|
1031
|
+
<div><span className="text-emerald-400">$</span> cd my-app</div>
|
|
1032
|
+
<div><span className="text-emerald-400">$</span> bun run dev</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
</div>
|
|
1035
|
+
|
|
1036
|
+
{/* CTA Buttons */}
|
|
1037
|
+
<div className="flex flex-wrap gap-4 justify-center mt-10 opacity-0 animate-slide-up delay-400">
|
|
1038
|
+
<a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">
|
|
1039
|
+
Get Started
|
|
744
1040
|
</a>
|
|
745
|
-
<a
|
|
746
|
-
|
|
747
|
-
target="_blank"
|
|
748
|
-
rel="noopener"
|
|
749
|
-
className="btn border-2 border-white text-white hover:bg-white/10"
|
|
750
|
-
>
|
|
751
|
-
View on GitHub
|
|
1041
|
+
<a href="/blog" className="btn btn-secondary text-base px-6 py-3">
|
|
1042
|
+
Read the Blog
|
|
752
1043
|
</a>
|
|
753
1044
|
</div>
|
|
754
1045
|
</div>
|
|
755
1046
|
</section>
|
|
756
1047
|
|
|
757
|
-
{/* Features
|
|
758
|
-
<section className="py-
|
|
1048
|
+
{/* Features */}
|
|
1049
|
+
<section className="py-20 px-4">
|
|
759
1050
|
<div className="max-w-6xl mx-auto">
|
|
760
|
-
<
|
|
761
|
-
|
|
762
|
-
<
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1051
|
+
<div className="text-center mb-16">
|
|
1052
|
+
<h2 className="text-3xl sm:text-4xl font-bold mb-4">Everything You Need</h2>
|
|
1053
|
+
<p className="text-gray-600 dark:text-gray-400 text-lg max-w-2xl mx-auto">
|
|
1054
|
+
EreoJS combines the best of React with Bun's performance to deliver a complete fullstack framework.
|
|
1055
|
+
</p>
|
|
1056
|
+
</div>
|
|
1057
|
+
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
1058
|
+
{[
|
|
1059
|
+
{ icon: 'bolt', title: 'Bun-Powered', desc: 'Lightning-fast startup, builds, and runtime. 10x faster than Node.js for common operations.' },
|
|
1060
|
+
{ icon: 'folder', title: 'File Routing', desc: 'Intuitive file-based routing with nested layouts, dynamic segments, and automatic code splitting.' },
|
|
1061
|
+
{ icon: 'server', title: 'Server-Side Rendering', desc: 'Stream HTML from the server for fast initial loads with full SEO support.' },
|
|
1062
|
+
{ icon: 'island', title: 'Islands Architecture', desc: 'Selective hydration means only interactive parts ship JavaScript to the client.' },
|
|
1063
|
+
{ icon: 'data', title: 'Loaders & Actions', desc: 'Simple async functions for server-side data loading and form handling.' },
|
|
1064
|
+
{ icon: 'ts', title: 'TypeScript First', desc: 'Full type safety with zero-config TypeScript. Types flow from server to client.' },
|
|
1065
|
+
].map((feature) => (
|
|
1066
|
+
<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">
|
|
1067
|
+
<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">
|
|
1068
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#6366f1" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
1069
|
+
{feature.icon === 'bolt' && <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />}
|
|
1070
|
+
{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" />}
|
|
1071
|
+
{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"/></>}
|
|
1072
|
+
{feature.icon === 'island' && <><circle cx="12" cy="12" r="10"/><path d="M8 12l2 2 4-4"/></>}
|
|
1073
|
+
{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"/></>}
|
|
1074
|
+
{feature.icon === 'ts' && <><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></>}
|
|
1075
|
+
</svg>
|
|
1076
|
+
</div>
|
|
1077
|
+
<h3 className="text-lg font-bold mb-2">{feature.title}</h3>
|
|
1078
|
+
<p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">{feature.desc}</p>
|
|
1079
|
+
</div>
|
|
1080
|
+
))}
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
</section>
|
|
1084
|
+
|
|
1085
|
+
{/* Code Showcase */}
|
|
1086
|
+
<section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
|
|
1087
|
+
<div className="max-w-6xl mx-auto">
|
|
1088
|
+
<div className="text-center mb-16">
|
|
1089
|
+
<h2 className="text-3xl sm:text-4xl font-bold mb-4">Simple, Powerful APIs</h2>
|
|
1090
|
+
<p className="text-gray-600 dark:text-gray-400 text-lg">Loaders fetch data. Actions handle mutations. It's that simple.</p>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
1093
|
+
<div className="code-window">
|
|
1094
|
+
<div className="code-window-header">
|
|
1095
|
+
<span className="code-window-dot bg-red-500" />
|
|
1096
|
+
<span className="code-window-dot bg-yellow-500" />
|
|
1097
|
+
<span className="code-window-dot bg-green-500" />
|
|
1098
|
+
<span className="ml-3 text-xs text-gray-500 font-mono">routes/users.tsx</span>
|
|
1099
|
+
</div>
|
|
1100
|
+
<pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
|
|
1101
|
+
{\`// Data runs on the server
|
|
1102
|
+
export async function loader() {
|
|
1103
|
+
const users = await db.user.findMany();
|
|
1104
|
+
return { users };
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Component renders on server + client
|
|
1108
|
+
export default function Users({ loaderData }) {
|
|
1109
|
+
return (
|
|
1110
|
+
<ul>
|
|
1111
|
+
{loaderData.users.map(user => (
|
|
1112
|
+
<li key={user.id}>{user.name}</li>
|
|
1113
|
+
))}
|
|
1114
|
+
</ul>
|
|
1115
|
+
);
|
|
1116
|
+
}\`}
|
|
1117
|
+
</pre>
|
|
775
1118
|
</div>
|
|
776
|
-
<div className="
|
|
777
|
-
<div className="
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1119
|
+
<div className="code-window">
|
|
1120
|
+
<div className="code-window-header">
|
|
1121
|
+
<span className="code-window-dot bg-red-500" />
|
|
1122
|
+
<span className="code-window-dot bg-yellow-500" />
|
|
1123
|
+
<span className="code-window-dot bg-green-500" />
|
|
1124
|
+
<span className="ml-3 text-xs text-gray-500 font-mono">routes/contact.tsx</span>
|
|
1125
|
+
</div>
|
|
1126
|
+
<pre className="px-5 py-4 font-mono text-sm text-primary-300 leading-relaxed overflow-x-auto">
|
|
1127
|
+
{\`// Actions handle form submissions
|
|
1128
|
+
export async function action({ request }) {
|
|
1129
|
+
const form = await request.formData();
|
|
1130
|
+
await db.message.create({
|
|
1131
|
+
data: {
|
|
1132
|
+
name: form.get('name'),
|
|
1133
|
+
email: form.get('email'),
|
|
1134
|
+
body: form.get('message'),
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
return { success: true };
|
|
1138
|
+
}\`}
|
|
1139
|
+
</pre>
|
|
782
1140
|
</div>
|
|
783
1141
|
</div>
|
|
784
1142
|
</div>
|
|
785
1143
|
</section>
|
|
786
1144
|
|
|
787
|
-
{/* Interactive Demo
|
|
788
|
-
<section className="py-
|
|
789
|
-
<div className="max-w-4xl mx-auto
|
|
790
|
-
<
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
<
|
|
1145
|
+
{/* Interactive Demo */}
|
|
1146
|
+
<section className="py-20 px-4">
|
|
1147
|
+
<div className="max-w-4xl mx-auto">
|
|
1148
|
+
<div className="card text-center border border-gray-200 dark:border-gray-700">
|
|
1149
|
+
<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">
|
|
1150
|
+
Interactive Island
|
|
1151
|
+
</div>
|
|
1152
|
+
<h2 className="text-3xl font-bold mb-3">Islands Architecture</h2>
|
|
1153
|
+
<p className="text-gray-600 dark:text-gray-400 mb-8 max-w-lg mx-auto">
|
|
1154
|
+
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.
|
|
1155
|
+
</p>
|
|
1156
|
+
<div className="flex justify-center">
|
|
1157
|
+
<Counter initialCount={0} />
|
|
1158
|
+
</div>
|
|
796
1159
|
</div>
|
|
797
1160
|
</div>
|
|
798
1161
|
</section>
|
|
799
1162
|
|
|
800
|
-
{/* Server Data
|
|
801
|
-
<section className="py-
|
|
1163
|
+
{/* Server Data */}
|
|
1164
|
+
<section className="py-20 px-4 bg-gray-50 dark:bg-gray-800/50">
|
|
802
1165
|
<div className="max-w-4xl mx-auto">
|
|
803
|
-
<div className="card">
|
|
804
|
-
<
|
|
1166
|
+
<div className="card border border-gray-200 dark:border-gray-700">
|
|
1167
|
+
<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">
|
|
1168
|
+
Server Loader Data
|
|
1169
|
+
</div>
|
|
1170
|
+
<h2 className="text-2xl font-bold mb-2">Loaded at Build Time</h2>
|
|
805
1171
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
|
806
|
-
This data was
|
|
1172
|
+
This data was fetched on the server via a loader function \u2014 zero client-side fetching.
|
|
807
1173
|
</p>
|
|
808
|
-
<div className="grid
|
|
809
|
-
<div className="p-
|
|
1174
|
+
<div className="grid sm:grid-cols-2 gap-4 mb-6">
|
|
1175
|
+
<div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
|
|
810
1176
|
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Blog Posts</div>
|
|
811
|
-
<div className="text-
|
|
1177
|
+
<div className="text-4xl font-extrabold">{stats.posts}</div>
|
|
812
1178
|
</div>
|
|
813
|
-
<div className="p-
|
|
1179
|
+
<div className="p-5 bg-gray-50 dark:bg-gray-700/50 rounded-xl">
|
|
814
1180
|
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">Rendered At</div>
|
|
815
|
-
<div className="text-
|
|
1181
|
+
<div className="text-4xl font-extrabold font-mono">{stats.serverTime}</div>
|
|
816
1182
|
</div>
|
|
817
1183
|
</div>
|
|
818
1184
|
{featuredPost && (
|
|
819
|
-
<div className="
|
|
1185
|
+
<div className="pt-6 border-t border-gray-200 dark:border-gray-700">
|
|
820
1186
|
<div className="text-sm text-gray-500 dark:text-gray-400 mb-2">Featured Post</div>
|
|
821
1187
|
<h3 className="text-xl font-bold mb-2">
|
|
822
|
-
<a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600">
|
|
1188
|
+
<a href={\`/blog/\${featuredPost.slug}\`} className="hover:text-primary-600 transition-colors">
|
|
823
1189
|
{featuredPost.title}
|
|
824
1190
|
</a>
|
|
825
1191
|
</h3>
|
|
@@ -829,6 +1195,26 @@ export default function HomePage({ loaderData }${ts ? ": HomePageProps" : ""}) {
|
|
|
829
1195
|
</div>
|
|
830
1196
|
</div>
|
|
831
1197
|
</section>
|
|
1198
|
+
|
|
1199
|
+
{/* CTA Footer */}
|
|
1200
|
+
<section className="py-24 px-4 text-center">
|
|
1201
|
+
<h2 className="text-3xl sm:text-4xl font-bold mb-4">Ready to Build?</h2>
|
|
1202
|
+
<p className="text-gray-600 dark:text-gray-400 text-lg mb-8">Get started with a single command.</p>
|
|
1203
|
+
<div className="code-window glow max-w-sm mx-auto mb-8">
|
|
1204
|
+
<div className="code-window-header">
|
|
1205
|
+
<span className="code-window-dot bg-red-500" />
|
|
1206
|
+
<span className="code-window-dot bg-yellow-500" />
|
|
1207
|
+
<span className="code-window-dot bg-green-500" />
|
|
1208
|
+
</div>
|
|
1209
|
+
<div className="px-5 py-3 font-mono text-sm text-primary-300">
|
|
1210
|
+
<span className="text-emerald-400">$</span> bunx create-ereo my-app
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
<div className="flex flex-wrap gap-4 justify-center">
|
|
1214
|
+
<a href="https://ereo.dev/docs" className="btn btn-primary text-base px-6 py-3">Documentation</a>
|
|
1215
|
+
<a href="https://github.com/ereo-js/ereo" target="_blank" rel="noopener" className="btn btn-secondary text-base px-6 py-3">GitHub</a>
|
|
1216
|
+
</div>
|
|
1217
|
+
</section>
|
|
832
1218
|
</div>
|
|
833
1219
|
);
|
|
834
1220
|
}
|
|
@@ -1438,8 +1824,18 @@ async function main() {
|
|
|
1438
1824
|
printHelp();
|
|
1439
1825
|
process.exit(1);
|
|
1440
1826
|
}
|
|
1827
|
+
if (/[<>:"|?*]/.test(projectName) || projectName.startsWith(".")) {
|
|
1828
|
+
console.error(` \x1B[31m\u2717\x1B[0m Invalid project name. Avoid special characters and leading dots.
|
|
1829
|
+
`);
|
|
1830
|
+
process.exit(1);
|
|
1831
|
+
}
|
|
1441
1832
|
const finalOptions = { ...defaultOptions, ...options };
|
|
1442
1833
|
const projectDir = resolve(process.cwd(), projectName);
|
|
1834
|
+
if (!projectDir.startsWith(process.cwd())) {
|
|
1835
|
+
console.error(` \x1B[31m\u2717\x1B[0m Invalid project name: path traversal detected.
|
|
1836
|
+
`);
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1443
1839
|
console.log(` Creating \x1B[36m${projectName}\x1B[0m...
|
|
1444
1840
|
`);
|
|
1445
1841
|
console.log(` Template: ${finalOptions.template}`);
|