@emberkit/cli 0.3.1 → 0.5.0
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/commands/create.js +36 -780
- package/dist/templates/api.js +280 -0
- package/dist/templates/blog.js +358 -0
- package/dist/templates/dashboard.js +407 -0
- package/dist/templates/index.js +179 -38
- package/dist/templates/minimal.js +89 -0
- package/dist/templates/projects.js +536 -0
- package/dist/templates/saas.js +539 -0
- package/dist/utils/filesystem.js +19 -0
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -2,6 +2,12 @@ import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { resolve, join } from "path";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { getPackageManager, getInstallCommand } from "../utils/filesystem.js";
|
|
5
|
+
import { starterFiles, withUiTemplate } from "../templates/projects.js";
|
|
6
|
+
import { minimalTemplate } from "../templates/minimal.js";
|
|
7
|
+
import { blogTemplate } from "../templates/blog.js";
|
|
8
|
+
import { saasTemplate } from "../templates/saas.js";
|
|
9
|
+
import { dashboardTemplate } from "../templates/dashboard.js";
|
|
10
|
+
import { apiTemplate } from "../templates/api.js";
|
|
5
11
|
const RESET = "\x1b[0m";
|
|
6
12
|
const BOLD = "\x1b[1m";
|
|
7
13
|
const DIM = "\x1b[2m";
|
|
@@ -11,7 +17,29 @@ const BRIGHT_GREEN = "\x1b[92m";
|
|
|
11
17
|
const BRIGHT_BLUE = "\x1b[94m";
|
|
12
18
|
const BRIGHT_CYAN = "\x1b[96m";
|
|
13
19
|
const BRIGHT_WHITE = "\x1b[97m";
|
|
20
|
+
const BRIGHT_YELLOW = "\x1b[93m";
|
|
14
21
|
const ORANGE_BG = "\x1b[48;5;208m";
|
|
22
|
+
const TEMPLATES = [
|
|
23
|
+
{ id: "basic", name: "Basic", desc: "Simple starter with Tailwind CSS", files: starterFiles },
|
|
24
|
+
{ id: "with-ui", name: "With UI", desc: "Starter with EmberKit UI components", files: withUiTemplate },
|
|
25
|
+
{ id: "minimal", name: "Minimal", desc: "Barebones project, no CSS framework", files: minimalTemplate },
|
|
26
|
+
{ id: "blog", name: "Blog", desc: "Blog with file-based routing and Tailwind", files: blogTemplate },
|
|
27
|
+
{ id: "saas", name: "SaaS", desc: "SaaS landing page with auth routes", files: saasTemplate },
|
|
28
|
+
{ id: "dashboard", name: "Dashboard", desc: "Admin dashboard with sidebar layout", files: dashboardTemplate },
|
|
29
|
+
{ id: "api", name: "API", desc: "REST API server with CRUD endpoints", files: apiTemplate },
|
|
30
|
+
];
|
|
31
|
+
function getTemplateById(id) {
|
|
32
|
+
return TEMPLATES.find((t) => t.id === id) ?? TEMPLATES[0];
|
|
33
|
+
}
|
|
34
|
+
function printTemplateList() {
|
|
35
|
+
console.log(`\n ${BRIGHT_WHITE + BOLD}Available templates:${RESET}\n`);
|
|
36
|
+
for (const t of TEMPLATES) {
|
|
37
|
+
const isDefault = t.id === "basic";
|
|
38
|
+
const label = isDefault ? ` ${BRIGHT_YELLOW}(default)${RESET}` : "";
|
|
39
|
+
console.log(` ${BRIGHT_CYAN}${t.id.padEnd(12)}${RESET} ${BRIGHT_WHITE}${t.name}${RESET}${label}`);
|
|
40
|
+
console.log(` ${DIM}${" ".repeat(16)}${t.desc}${RESET}\n`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
15
43
|
function printHeader() {
|
|
16
44
|
const header = `
|
|
17
45
|
${BRIGHT_BLACK}╭─────────────────────────────────────────────────────╮${RESET}
|
|
@@ -56,512 +84,22 @@ function getNpmPackageName(name) {
|
|
|
56
84
|
const kebab = toKebabCase(name);
|
|
57
85
|
return kebab.startsWith("@") ? kebab : kebab.replace(/^emberkit-/, "");
|
|
58
86
|
}
|
|
59
|
-
const starterFiles = {
|
|
60
|
-
"package.json": `{
|
|
61
|
-
"name": "{{name}}",
|
|
62
|
-
"version": "0.1.0",
|
|
63
|
-
"private": true,
|
|
64
|
-
"type": "module",
|
|
65
|
-
"scripts": {
|
|
66
|
-
"dev": "emberkit dev",
|
|
67
|
-
"build": "emberkit build",
|
|
68
|
-
"preview": "emberkit preview",
|
|
69
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
70
|
-
"format": "prettier --write \\"src/**/*.{ts,tsx}\\""
|
|
71
|
-
},
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"@emberkit/core": "^0.2.4"
|
|
74
|
-
},
|
|
75
|
-
"devDependencies": {
|
|
76
|
-
"@emberkit/cli": "^0.2.4",
|
|
77
|
-
"typescript": "^5.7.0",
|
|
78
|
-
"vite": "^6.0.0"
|
|
79
|
-
}
|
|
80
|
-
}`,
|
|
81
|
-
"tsconfig.json": `{
|
|
82
|
-
"compilerOptions": {
|
|
83
|
-
"target": "ES2022",
|
|
84
|
-
"module": "ESNext",
|
|
85
|
-
"moduleResolution": "bundler",
|
|
86
|
-
"jsx": "react-jsx",
|
|
87
|
-
"jsxImportSource": "@emberkit/core",
|
|
88
|
-
"strict": true,
|
|
89
|
-
"esModuleInterop": true,
|
|
90
|
-
"skipLibCheck": true,
|
|
91
|
-
"forceConsistentCasingInFileNames": true,
|
|
92
|
-
"resolveJsonModule": true,
|
|
93
|
-
"isolatedModules": true,
|
|
94
|
-
"noEmit": true,
|
|
95
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
96
|
-
"paths": {
|
|
97
|
-
"@/*": ["./src/*"]
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
"include": ["src"],
|
|
101
|
-
"exclude": ["node_modules", "dist"]
|
|
102
|
-
}`,
|
|
103
|
-
"emberkit.config.ts": `import { defineConfig } from '@emberkit/core';
|
|
104
|
-
|
|
105
|
-
export default defineConfig({
|
|
106
|
-
mode: 'spa',
|
|
107
|
-
build: {
|
|
108
|
-
outDir: 'dist',
|
|
109
|
-
target: 'esnext',
|
|
110
|
-
},
|
|
111
|
-
});`,
|
|
112
|
-
"vite.config.ts": `import { defineConfig } from 'vite';
|
|
113
|
-
import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
|
|
114
|
-
|
|
115
|
-
export default defineConfig({
|
|
116
|
-
plugins: [emberkitVitePlugin()],
|
|
117
|
-
server: {
|
|
118
|
-
port: 3000,
|
|
119
|
-
host: 'localhost',
|
|
120
|
-
},
|
|
121
|
-
esbuild: {
|
|
122
|
-
jsxImportSource: '@emberkit/core',
|
|
123
|
-
},
|
|
124
|
-
});`,
|
|
125
|
-
"index.html": `<!DOCTYPE html>
|
|
126
|
-
<html lang="en">
|
|
127
|
-
<head>
|
|
128
|
-
<meta charset="UTF-8">
|
|
129
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
130
|
-
<title>{{name}}</title>
|
|
131
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
132
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
133
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
134
|
-
<style>
|
|
135
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
136
|
-
body { font-family: 'Inter', sans-serif; background: #0f172a; color: #e2e8f0; }
|
|
137
|
-
a { color: inherit; text-decoration: none; }
|
|
138
|
-
</style>
|
|
139
|
-
</head>
|
|
140
|
-
<body>
|
|
141
|
-
<div id="app"></div>
|
|
142
|
-
<script type="module" src="/src/index.tsx"></script>
|
|
143
|
-
</body>
|
|
144
|
-
</html>`,
|
|
145
|
-
"src/index.tsx": `import { render } from '@emberkit/core';
|
|
146
|
-
import App from './routes/_layout';
|
|
147
|
-
|
|
148
|
-
const root = document.getElementById('app');
|
|
149
|
-
|
|
150
|
-
if (root) {
|
|
151
|
-
render(App, root);
|
|
152
|
-
}`,
|
|
153
|
-
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
154
|
-
import { Head } from '@emberkit/core';
|
|
155
|
-
|
|
156
|
-
const Layout: RouteComponent = ({ children }) => {
|
|
157
|
-
return (
|
|
158
|
-
<>
|
|
159
|
-
<Head>
|
|
160
|
-
<title>{{name}}</title>
|
|
161
|
-
<meta name="description" content="Built with EmberKit" />
|
|
162
|
-
</Head>
|
|
163
|
-
<div className="app">
|
|
164
|
-
<header className="header">
|
|
165
|
-
<div className="logo">
|
|
166
|
-
<span className="logo-icon">🔥</span>
|
|
167
|
-
<span className="logo-text">{{name}}</span>
|
|
168
|
-
</div>
|
|
169
|
-
<nav className="nav">
|
|
170
|
-
<a href="/" className="nav-link">Home</a>
|
|
171
|
-
<a href="/about" className="nav-link">About</a>
|
|
172
|
-
<a href="https://emberkit.dev/docs" className="nav-link" target="_blank">Docs →</a>
|
|
173
|
-
</nav>
|
|
174
|
-
</header>
|
|
175
|
-
<main className="main">{children}</main>
|
|
176
|
-
<footer className="footer">
|
|
177
|
-
<p>Built with <a href="https://emberkit.dev" className="footer-link">EmberKit</a></p>
|
|
178
|
-
</footer>
|
|
179
|
-
</div>
|
|
180
|
-
</>
|
|
181
|
-
);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
export default Layout;`,
|
|
185
|
-
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
186
|
-
import { signal } from '@emberkit/core';
|
|
187
|
-
|
|
188
|
-
const HomePage: RouteComponent = () => {
|
|
189
|
-
const count = signal(0);
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<div className="home">
|
|
193
|
-
<section className="hero">
|
|
194
|
-
<h1 className="hero-title">
|
|
195
|
-
Welcome to <span className="gradient-text">{{name}}</span>
|
|
196
|
-
</h1>
|
|
197
|
-
<p className="hero-desc">
|
|
198
|
-
A minimalist TypeScript-first JSX framework built for speed and simplicity.
|
|
199
|
-
Get started in seconds with hot module replacement and zero-config routing.
|
|
200
|
-
</p>
|
|
201
|
-
<div className="hero-actions">
|
|
202
|
-
<a href="/about" className="btn btn-primary">
|
|
203
|
-
Learn More
|
|
204
|
-
</a>
|
|
205
|
-
<a href="https://emberkit.dev/docs" target="_blank" className="btn btn-secondary">
|
|
206
|
-
Read Docs →
|
|
207
|
-
</a>
|
|
208
|
-
</div>
|
|
209
|
-
</section>
|
|
210
|
-
|
|
211
|
-
<section className="features">
|
|
212
|
-
<div className="feature-card">
|
|
213
|
-
<div className="feature-icon">⚡</div>
|
|
214
|
-
<h3>Lightning Fast</h3>
|
|
215
|
-
<p>Sub-10KB runtime with tree-shakeable architecture</p>
|
|
216
|
-
</div>
|
|
217
|
-
<div className="feature-card">
|
|
218
|
-
<div className="feature-icon">🔷</div>
|
|
219
|
-
<h3>TypeScript First</h3>
|
|
220
|
-
<p>Full type safety with intelligent autocomplete</p>
|
|
221
|
-
</div>
|
|
222
|
-
<div className="feature-card">
|
|
223
|
-
<div className="feature-icon">🛤️</div>
|
|
224
|
-
<h3>File-Based Routing</h3>
|
|
225
|
-
<p>Routes automatically created from your file structure</p>
|
|
226
|
-
</div>
|
|
227
|
-
</section>
|
|
228
|
-
|
|
229
|
-
<section className="counter">
|
|
230
|
-
<h2>Try the Counter</h2>
|
|
231
|
-
<div className="counter-display">
|
|
232
|
-
<button
|
|
233
|
-
className="counter-btn"
|
|
234
|
-
onClick={() => count.value--}
|
|
235
|
-
>
|
|
236
|
-
−
|
|
237
|
-
</button>
|
|
238
|
-
<span className="counter-value">{count}</span>
|
|
239
|
-
<button
|
|
240
|
-
className="counter-btn"
|
|
241
|
-
onClick={() => count.value++}
|
|
242
|
-
>
|
|
243
|
-
+
|
|
244
|
-
</button>
|
|
245
|
-
</div>
|
|
246
|
-
</section>
|
|
247
|
-
</div>
|
|
248
|
-
);
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
export default HomePage;`,
|
|
252
|
-
"src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
253
|
-
import { Head } from '@emberkit/core';
|
|
254
|
-
|
|
255
|
-
const AboutPage: RouteComponent = () => {
|
|
256
|
-
return (
|
|
257
|
-
<div className="about">
|
|
258
|
-
<Head>
|
|
259
|
-
<title>About - {{name}}</title>
|
|
260
|
-
</Head>
|
|
261
|
-
<div className="about-content">
|
|
262
|
-
<h1>About {{name}}</h1>
|
|
263
|
-
<p>
|
|
264
|
-
EmberKit is a minimalist TypeScript-first JSX framework built for speed and simplicity.
|
|
265
|
-
It combines the best of modern frontend development with a lightweight runtime.
|
|
266
|
-
</p>
|
|
267
|
-
<div className="about-features">
|
|
268
|
-
<div className="about-feature">
|
|
269
|
-
<span className="feature-badge">SPA & SSR</span>
|
|
270
|
-
<span>Works in both modes</span>
|
|
271
|
-
</div>
|
|
272
|
-
<div className="about-feature">
|
|
273
|
-
<span className="feature-badge">Zero Config</span>
|
|
274
|
-
<span>Sensible defaults</span>
|
|
275
|
-
</div>
|
|
276
|
-
<div className="about-feature">
|
|
277
|
-
<span className="feature-badge">HMR</span>
|
|
278
|
-
<span>Hot module replacement</span>
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
<a href="/" className="back-link">← Back to Home</a>
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
export default AboutPage;`,
|
|
288
|
-
"src/styles.css": `.app {
|
|
289
|
-
min-height: 100vh;
|
|
290
|
-
display: flex;
|
|
291
|
-
flex-direction: column;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.header {
|
|
295
|
-
display: flex;
|
|
296
|
-
justify-content: space-between;
|
|
297
|
-
align-items: center;
|
|
298
|
-
padding: 1.5rem 2rem;
|
|
299
|
-
border-bottom: 1px solid #1e293b;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.logo {
|
|
303
|
-
display: flex;
|
|
304
|
-
align-items: center;
|
|
305
|
-
gap: 0.5rem;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
.logo-icon {
|
|
309
|
-
font-size: 1.5rem;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.logo-text {
|
|
313
|
-
font-weight: 700;
|
|
314
|
-
font-size: 1.25rem;
|
|
315
|
-
background: linear-gradient(135deg, #f97316, #fb923c);
|
|
316
|
-
-webkit-background-clip: text;
|
|
317
|
-
-webkit-text-fill-color: transparent;
|
|
318
|
-
background-clip: text;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.nav {
|
|
322
|
-
display: flex;
|
|
323
|
-
gap: 1.5rem;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.nav-link {
|
|
327
|
-
color: #94a3b8;
|
|
328
|
-
font-weight: 500;
|
|
329
|
-
transition: color 0.2s;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.nav-link:hover {
|
|
333
|
-
color: #f97316;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.main {
|
|
337
|
-
flex: 1;
|
|
338
|
-
max-width: 1200px;
|
|
339
|
-
width: 100%;
|
|
340
|
-
margin: 0 auto;
|
|
341
|
-
padding: 3rem 2rem;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.footer {
|
|
345
|
-
padding: 2rem;
|
|
346
|
-
text-align: center;
|
|
347
|
-
border-top: 1px solid #1e293b;
|
|
348
|
-
color: #64748b;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
.footer-link {
|
|
352
|
-
color: #f97316;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.home {
|
|
356
|
-
display: flex;
|
|
357
|
-
flex-direction: column;
|
|
358
|
-
gap: 4rem;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.hero {
|
|
362
|
-
text-align: center;
|
|
363
|
-
padding: 2rem 0;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
.hero-title {
|
|
367
|
-
font-size: 3rem;
|
|
368
|
-
font-weight: 800;
|
|
369
|
-
margin-bottom: 1.5rem;
|
|
370
|
-
line-height: 1.1;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
.gradient-text {
|
|
374
|
-
background: linear-gradient(135deg, #f97316, #fb923c, #fdba74);
|
|
375
|
-
-webkit-background-clip: text;
|
|
376
|
-
-webkit-text-fill-color: transparent;
|
|
377
|
-
background-clip: text;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
.hero-desc {
|
|
381
|
-
font-size: 1.25rem;
|
|
382
|
-
color: #94a3b8;
|
|
383
|
-
max-width: 600px;
|
|
384
|
-
margin: 0 auto 2rem;
|
|
385
|
-
line-height: 1.6;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.hero-actions {
|
|
389
|
-
display: flex;
|
|
390
|
-
gap: 1rem;
|
|
391
|
-
justify-content: center;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
.btn {
|
|
395
|
-
display: inline-block;
|
|
396
|
-
padding: 0.875rem 1.75rem;
|
|
397
|
-
border-radius: 0.5rem;
|
|
398
|
-
font-weight: 600;
|
|
399
|
-
transition: all 0.2s;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.btn-primary {
|
|
403
|
-
background: #f97316;
|
|
404
|
-
color: white;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
.btn-primary:hover {
|
|
408
|
-
background: #ea580c;
|
|
409
|
-
transform: translateY(-1px);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
.btn-secondary {
|
|
413
|
-
background: #1e293b;
|
|
414
|
-
color: #e2e8f0;
|
|
415
|
-
border: 1px solid #334155;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
.btn-secondary:hover {
|
|
419
|
-
background: #334155;
|
|
420
|
-
border-color: #475569;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.features {
|
|
424
|
-
display: grid;
|
|
425
|
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
426
|
-
gap: 1.5rem;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.feature-card {
|
|
430
|
-
background: #1e293b;
|
|
431
|
-
border: 1px solid #334155;
|
|
432
|
-
border-radius: 0.75rem;
|
|
433
|
-
padding: 1.5rem;
|
|
434
|
-
transition: all 0.2s;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
.feature-card:hover {
|
|
438
|
-
border-color: #f97316;
|
|
439
|
-
transform: translateY(-2px);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
.feature-icon {
|
|
443
|
-
font-size: 2rem;
|
|
444
|
-
margin-bottom: 1rem;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.feature-card h3 {
|
|
448
|
-
font-size: 1.125rem;
|
|
449
|
-
font-weight: 600;
|
|
450
|
-
margin-bottom: 0.5rem;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.feature-card p {
|
|
454
|
-
color: #64748b;
|
|
455
|
-
font-size: 0.875rem;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.counter {
|
|
459
|
-
text-align: center;
|
|
460
|
-
padding: 2rem;
|
|
461
|
-
background: #1e293b;
|
|
462
|
-
border-radius: 1rem;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.counter h2 {
|
|
466
|
-
margin-bottom: 1.5rem;
|
|
467
|
-
font-size: 1.5rem;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
.counter-display {
|
|
471
|
-
display: flex;
|
|
472
|
-
align-items: center;
|
|
473
|
-
justify-content: center;
|
|
474
|
-
gap: 1.5rem;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
.counter-btn {
|
|
478
|
-
width: 48px;
|
|
479
|
-
height: 48px;
|
|
480
|
-
border-radius: 0.5rem;
|
|
481
|
-
border: 1px solid #334155;
|
|
482
|
-
background: #0f172a;
|
|
483
|
-
color: #f97316;
|
|
484
|
-
font-size: 1.5rem;
|
|
485
|
-
cursor: pointer;
|
|
486
|
-
transition: all 0.2s;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.counter-btn:hover {
|
|
490
|
-
background: #f97316;
|
|
491
|
-
color: white;
|
|
492
|
-
border-color: #f97316;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
.counter-value {
|
|
496
|
-
font-size: 2.5rem;
|
|
497
|
-
font-weight: 700;
|
|
498
|
-
min-width: 60px;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
.about {
|
|
502
|
-
max-width: 700px;
|
|
503
|
-
margin: 0 auto;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
.about h1 {
|
|
507
|
-
font-size: 2rem;
|
|
508
|
-
font-weight: 700;
|
|
509
|
-
margin-bottom: 1.5rem;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
.about > div > p {
|
|
513
|
-
color: #94a3b8;
|
|
514
|
-
font-size: 1.125rem;
|
|
515
|
-
line-height: 1.7;
|
|
516
|
-
margin-bottom: 2rem;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
.about-features {
|
|
520
|
-
display: flex;
|
|
521
|
-
gap: 1rem;
|
|
522
|
-
flex-wrap: wrap;
|
|
523
|
-
margin-bottom: 2rem;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.about-feature {
|
|
527
|
-
display: flex;
|
|
528
|
-
align-items: center;
|
|
529
|
-
gap: 0.5rem;
|
|
530
|
-
background: #1e293b;
|
|
531
|
-
padding: 0.75rem 1rem;
|
|
532
|
-
border-radius: 0.5rem;
|
|
533
|
-
font-size: 0.875rem;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
.feature-badge {
|
|
537
|
-
background: #f97316;
|
|
538
|
-
color: white;
|
|
539
|
-
padding: 0.125rem 0.5rem;
|
|
540
|
-
border-radius: 0.25rem;
|
|
541
|
-
font-size: 0.75rem;
|
|
542
|
-
font-weight: 600;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
.back-link {
|
|
546
|
-
display: inline-block;
|
|
547
|
-
color: #f97316;
|
|
548
|
-
font-weight: 500;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
.back-link:hover {
|
|
552
|
-
text-decoration: underline;
|
|
553
|
-
}`,
|
|
554
|
-
};
|
|
555
87
|
export async function create(options) {
|
|
556
88
|
printHeader();
|
|
557
89
|
const { name, noInstall = false } = options;
|
|
558
90
|
const directory = options.directory ?? toKebabCase(name);
|
|
559
91
|
const targetDir = resolve(process.cwd(), directory);
|
|
560
92
|
const templateId = options.template || "basic";
|
|
93
|
+
const template = getTemplateById(templateId);
|
|
94
|
+
if (templateId !== "basic" && !TEMPLATES.find((t) => t.id === templateId)) {
|
|
95
|
+
printError(`Template "${templateId}" not found.`);
|
|
96
|
+
printTemplateList();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
561
99
|
printStep(1, 3, "Collecting project info");
|
|
562
100
|
console.log(` ${DIM}Project name:${RESET} ${BRIGHT_WHITE + name + RESET}`);
|
|
563
101
|
console.log(` ${DIM}Directory:${RESET} ${BRIGHT_WHITE + directory + RESET}`);
|
|
564
|
-
console.log(` ${DIM}Template:${RESET} ${BRIGHT_WHITE +
|
|
102
|
+
console.log(` ${DIM}Template:${RESET} ${BRIGHT_WHITE + template.name + RESET} (${template.id})\n`);
|
|
565
103
|
if (existsSync(targetDir)) {
|
|
566
104
|
printError(`Directory "${directory}" already exists.`);
|
|
567
105
|
process.exit(1);
|
|
@@ -574,7 +112,7 @@ export async function create(options) {
|
|
|
574
112
|
packageName: getNpmPackageName(name),
|
|
575
113
|
kebabName: toKebabCase(name),
|
|
576
114
|
};
|
|
577
|
-
const templateFiles =
|
|
115
|
+
const templateFiles = template.files;
|
|
578
116
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
579
117
|
const fullPath = join(targetDir, filePath);
|
|
580
118
|
const dir = join(targetDir, filePath.split("/").slice(0, -1).join("/"));
|
|
@@ -613,285 +151,3 @@ export async function create(options) {
|
|
|
613
151
|
console.log(` ${DIM}To preview the build:${RESET}`);
|
|
614
152
|
console.log(` ${BRIGHT_CYAN}emberkit preview${RESET}\n`);
|
|
615
153
|
}
|
|
616
|
-
const withUiTemplate = {
|
|
617
|
-
"package.json": `{
|
|
618
|
-
"name": "{{name}}",
|
|
619
|
-
"version": "0.1.0",
|
|
620
|
-
"private": true,
|
|
621
|
-
"type": "module",
|
|
622
|
-
"scripts": {
|
|
623
|
-
"dev": "emberkit dev",
|
|
624
|
-
"build": "emberkit build",
|
|
625
|
-
"preview": "emberkit preview",
|
|
626
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
627
|
-
"format": "prettier --write \\"src/**/*.{ts,tsx}\\""
|
|
628
|
-
},
|
|
629
|
-
"dependencies": {
|
|
630
|
-
"@emberkit/core": "^0.2.4",
|
|
631
|
-
"@emberkit/ui": "^0.2.3"
|
|
632
|
-
},
|
|
633
|
-
"devDependencies": {
|
|
634
|
-
"@emberkit/cli": "^0.2.4",
|
|
635
|
-
"typescript": "^5.7.0",
|
|
636
|
-
"vite": "^6.0.0",
|
|
637
|
-
"tailwindcss": "^4.0.0",
|
|
638
|
-
"@tailwindcss/vite": "^4.0.0"
|
|
639
|
-
}
|
|
640
|
-
}`,
|
|
641
|
-
"tsconfig.json": `{
|
|
642
|
-
"compilerOptions": {
|
|
643
|
-
"target": "ES2022",
|
|
644
|
-
"module": "ESNext",
|
|
645
|
-
"moduleResolution": "bundler",
|
|
646
|
-
"jsx": "react-jsx",
|
|
647
|
-
"jsxImportSource": "@emberkit/core",
|
|
648
|
-
"strict": true,
|
|
649
|
-
"esModuleInterop": true,
|
|
650
|
-
"skipLibCheck": true,
|
|
651
|
-
"forceConsistentCasingInFileNames": true,
|
|
652
|
-
"resolveJsonModule": true,
|
|
653
|
-
"isolatedModules": true,
|
|
654
|
-
"noEmit": true,
|
|
655
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
656
|
-
"paths": {
|
|
657
|
-
"@/*": ["./src/*"]
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
"include": ["src"],
|
|
661
|
-
"exclude": ["node_modules", "dist"]
|
|
662
|
-
}`,
|
|
663
|
-
"emberkit.config.ts": `import { defineConfig } from '@emberkit/core';
|
|
664
|
-
|
|
665
|
-
export default defineConfig({
|
|
666
|
-
mode: 'spa',
|
|
667
|
-
build: {
|
|
668
|
-
outDir: 'dist',
|
|
669
|
-
target: 'esnext',
|
|
670
|
-
},
|
|
671
|
-
});`,
|
|
672
|
-
"vite.config.ts": `import { defineConfig } from 'vite';
|
|
673
|
-
import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
|
|
674
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
675
|
-
|
|
676
|
-
export default defineConfig({
|
|
677
|
-
plugins: [emberkitVitePlugin(), tailwindcss()],
|
|
678
|
-
server: {
|
|
679
|
-
port: 3000,
|
|
680
|
-
host: 'localhost',
|
|
681
|
-
},
|
|
682
|
-
esbuild: {
|
|
683
|
-
jsxImportSource: '@emberkit/core',
|
|
684
|
-
},
|
|
685
|
-
});`,
|
|
686
|
-
"index.html": `<!DOCTYPE html>
|
|
687
|
-
<html lang="en">
|
|
688
|
-
<head>
|
|
689
|
-
<meta charset="UTF-8">
|
|
690
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
691
|
-
<title>{{name}}</title>
|
|
692
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
693
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
694
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
695
|
-
<link href="/styles.css" rel="stylesheet">
|
|
696
|
-
</head>
|
|
697
|
-
<body>
|
|
698
|
-
<div id="app"></div>
|
|
699
|
-
<script type="module" src="/src/index.tsx"></script>
|
|
700
|
-
</body>
|
|
701
|
-
</html>`,
|
|
702
|
-
"src/index.tsx": `import { render } from '@emberkit/core';
|
|
703
|
-
import App from './routes/_layout';
|
|
704
|
-
|
|
705
|
-
const root = document.getElementById('app');
|
|
706
|
-
|
|
707
|
-
if (root) {
|
|
708
|
-
render(App, root);
|
|
709
|
-
}`,
|
|
710
|
-
"src/styles.css": `@import "tailwindcss";
|
|
711
|
-
|
|
712
|
-
@theme {
|
|
713
|
-
--color-ember-50: #fff7ed;
|
|
714
|
-
--color-ember-100: #ffedd5;
|
|
715
|
-
--color-ember-200: #fed7aa;
|
|
716
|
-
--color-ember-300: #fdba74;
|
|
717
|
-
--color-ember-400: #fb923c;
|
|
718
|
-
--color-ember-500: #f97316;
|
|
719
|
-
--color-ember-600: #ea580c;
|
|
720
|
-
--color-ember-700: #c2410c;
|
|
721
|
-
--color-ember-800: #9a3412;
|
|
722
|
-
--color-ember-900: #7c2d12;
|
|
723
|
-
--font-family-sans: 'Inter', system-ui, sans-serif;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
body {
|
|
727
|
-
@apply bg-slate-900 text-slate-200 font-sans;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
a {
|
|
731
|
-
@apply text-inherit no-underline;
|
|
732
|
-
}`,
|
|
733
|
-
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
734
|
-
import { Head } from '@emberkit/core';
|
|
735
|
-
import { DefaultLayout } from '@emberkit/ui';
|
|
736
|
-
|
|
737
|
-
const Layout: RouteComponent = ({ children }) => {
|
|
738
|
-
return (
|
|
739
|
-
<>
|
|
740
|
-
<Head>
|
|
741
|
-
<title>{{name}}</title>
|
|
742
|
-
<meta name="description" content="Built with EmberKit UI" />
|
|
743
|
-
</Head>
|
|
744
|
-
<DefaultLayout
|
|
745
|
-
logo={<span className="text-ember-500 font-bold text-xl">🔥 {{name}}</span>}
|
|
746
|
-
navItems={[
|
|
747
|
-
{ label: 'Home', href: '/' },
|
|
748
|
-
{ label: 'About', href: '/about' },
|
|
749
|
-
{ label: 'Docs', href: 'https://emberkit.dev/docs', external: true },
|
|
750
|
-
]}
|
|
751
|
-
>
|
|
752
|
-
{children}
|
|
753
|
-
</DefaultLayout>
|
|
754
|
-
</>
|
|
755
|
-
);
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
export default Layout;`,
|
|
759
|
-
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
760
|
-
import { Button, Card, Heading, Text, Badge, Input } from '@emberkit/ui';
|
|
761
|
-
import { signal } from '@emberkit/core';
|
|
762
|
-
|
|
763
|
-
const HomePage: RouteComponent = () => {
|
|
764
|
-
const email = signal('');
|
|
765
|
-
|
|
766
|
-
return (
|
|
767
|
-
<div className="space-y-16">
|
|
768
|
-
<section className="text-center py-16">
|
|
769
|
-
<Heading level="h1" size="4xl" weight="bold" className="mb-4">
|
|
770
|
-
Welcome to <span className="text-ember-500">{{name}}</span>
|
|
771
|
-
</Heading>
|
|
772
|
-
<Text size="xl" color="muted" className="max-w-2xl mx-auto mb-8">
|
|
773
|
-
A modern starter template with EmberKit UI components and Tailwind CSS.
|
|
774
|
-
Build beautiful interfaces with our pre-built component library.
|
|
775
|
-
</Text>
|
|
776
|
-
<div className="flex gap-4 justify-center">
|
|
777
|
-
<Button variant="primary" size="lg">
|
|
778
|
-
Get Started
|
|
779
|
-
</Button>
|
|
780
|
-
<Button variant="secondary" size="lg">
|
|
781
|
-
View Docs
|
|
782
|
-
</Button>
|
|
783
|
-
</div>
|
|
784
|
-
</section>
|
|
785
|
-
|
|
786
|
-
<section>
|
|
787
|
-
<Heading level="h2" size="2xl" weight="semibold" className="mb-8 text-center">
|
|
788
|
-
UI Components
|
|
789
|
-
</Heading>
|
|
790
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
791
|
-
<Card padding="lg">
|
|
792
|
-
<Badge variant="primary" size="sm" className="mb-2">Button</Badge>
|
|
793
|
-
<Heading level="h3" size="lg" weight="semibold" className="mb-2">
|
|
794
|
-
Button Variants
|
|
795
|
-
</Heading>
|
|
796
|
-
<Text color="muted" className="mb-4">
|
|
797
|
-
Primary, secondary, ghost, and more button styles.
|
|
798
|
-
</Text>
|
|
799
|
-
<div className="flex gap-2 flex-wrap">
|
|
800
|
-
<Button variant="primary">Primary</Button>
|
|
801
|
-
<Button variant="secondary">Secondary</Button>
|
|
802
|
-
<Button variant="ghost">Ghost</Button>
|
|
803
|
-
</div>
|
|
804
|
-
</Card>
|
|
805
|
-
|
|
806
|
-
<Card padding="lg">
|
|
807
|
-
<Badge variant="success" size="sm" className="mb-2">Cards</Badge>
|
|
808
|
-
<Heading level="h3" size="lg" weight="semibold" className="mb-2">
|
|
809
|
-
Card Component
|
|
810
|
-
</Heading>
|
|
811
|
-
<Text color="muted" className="mb-4">
|
|
812
|
-
Flexible card layout with padding variants.
|
|
813
|
-
</Text>
|
|
814
|
-
<Card padding="md" className="bg-slate-800">
|
|
815
|
-
<Text>Card content here</Text>
|
|
816
|
-
</Card>
|
|
817
|
-
</Card>
|
|
818
|
-
|
|
819
|
-
<Card padding="lg">
|
|
820
|
-
<Badge variant="info" size="sm" className="mb-2">Forms</Badge>
|
|
821
|
-
<Heading level="h3" size="lg" weight="semibold" className="mb-2">
|
|
822
|
-
Form Inputs
|
|
823
|
-
</Heading>
|
|
824
|
-
<Text color="muted" className="mb-4">
|
|
825
|
-
Styled input with label support.
|
|
826
|
-
</Text>
|
|
827
|
-
<Input
|
|
828
|
-
label="Email"
|
|
829
|
-
placeholder="Enter your email"
|
|
830
|
-
value={email.value}
|
|
831
|
-
onChange={(e) => { email.value = e.currentTarget.value; }}
|
|
832
|
-
/>
|
|
833
|
-
</Card>
|
|
834
|
-
</div>
|
|
835
|
-
</section>
|
|
836
|
-
|
|
837
|
-
<section className="text-center py-16 bg-slate-800/50 rounded-xl">
|
|
838
|
-
<Heading level="h2" size="2xl" weight="semibold" className="mb-4">
|
|
839
|
-
Ready to get started?
|
|
840
|
-
</Heading>
|
|
841
|
-
<Text color="muted" className="max-w-xl mx-auto mb-6">
|
|
842
|
-
Install dependencies and start building your next project with EmberKit.
|
|
843
|
-
</Text>
|
|
844
|
-
<Button variant="primary" size="lg">
|
|
845
|
-
Create Project →
|
|
846
|
-
</Button>
|
|
847
|
-
</section>
|
|
848
|
-
</div>
|
|
849
|
-
);
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
export default HomePage;`,
|
|
853
|
-
"src/routes/about.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
854
|
-
import { Head } from '@emberkit/core';
|
|
855
|
-
import { Heading, Text, Button } from '@emberkit/ui';
|
|
856
|
-
|
|
857
|
-
const AboutPage: RouteComponent = () => {
|
|
858
|
-
return (
|
|
859
|
-
<div className="max-w-2xl mx-auto py-12">
|
|
860
|
-
<Head>
|
|
861
|
-
<title>About - {{name}}</title>
|
|
862
|
-
</Head>
|
|
863
|
-
<Heading level="h1" size="3xl" weight="bold" className="mb-6">
|
|
864
|
-
About {{name}}
|
|
865
|
-
</Heading>
|
|
866
|
-
<Text size="lg" color="muted" className="mb-8">
|
|
867
|
-
This project was created with EmberKit and the UI component library.
|
|
868
|
-
It demonstrates how to build modern, beautiful interfaces with our
|
|
869
|
-
pre-built components and Tailwind CSS.
|
|
870
|
-
</Text>
|
|
871
|
-
<div className="space-y-4">
|
|
872
|
-
<div className="flex items-center gap-3 p-4 bg-slate-800 rounded-lg">
|
|
873
|
-
<span className="text-ember-500 text-2xl">✓</span>
|
|
874
|
-
<Text>TypeScript-first development</Text>
|
|
875
|
-
</div>
|
|
876
|
-
<div className="flex items-center gap-3 p-4 bg-slate-800 rounded-lg">
|
|
877
|
-
<span className="text-ember-500 text-2xl">✓</span>
|
|
878
|
-
<Text>Pre-built UI components</Text>
|
|
879
|
-
</div>
|
|
880
|
-
<div className="flex items-center gap-3 p-4 bg-slate-800 rounded-lg">
|
|
881
|
-
<span className="text-ember-500 text-2xl">✓</span>
|
|
882
|
-
<Text>Tailwind CSS integration</Text>
|
|
883
|
-
</div>
|
|
884
|
-
<div className="flex items-center gap-3 p-4 bg-slate-800 rounded-lg">
|
|
885
|
-
<span className="text-ember-500 text-2xl">✓</span>
|
|
886
|
-
<Text>File-based routing</Text>
|
|
887
|
-
</div>
|
|
888
|
-
</div>
|
|
889
|
-
<div className="mt-8">
|
|
890
|
-
<Button variant="secondary">← Back to Home</Button>
|
|
891
|
-
</div>
|
|
892
|
-
</div>
|
|
893
|
-
);
|
|
894
|
-
};
|
|
895
|
-
|
|
896
|
-
export default AboutPage;`,
|
|
897
|
-
};
|