@emberkit/cli 0.4.0 → 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 -523
- 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/package.json +1 -1
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
export const dashboardTemplate = {
|
|
2
|
+
"package.json": `{
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "emberkit dev",
|
|
9
|
+
"build": "emberkit build",
|
|
10
|
+
"preview": "emberkit preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@emberkit/core": "^0.2.4",
|
|
14
|
+
"@emberkit/ui": "^0.2.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@emberkit/cli": "^0.2.4",
|
|
18
|
+
"typescript": "^5.7.0",
|
|
19
|
+
"vite": "^6.0.0",
|
|
20
|
+
"tailwindcss": "^4.0.0",
|
|
21
|
+
"@tailwindcss/vite": "^4.0.0"
|
|
22
|
+
}
|
|
23
|
+
}`,
|
|
24
|
+
"tsconfig.json": `{
|
|
25
|
+
"compilerOptions": {
|
|
26
|
+
"target": "ES2022",
|
|
27
|
+
"module": "ESNext",
|
|
28
|
+
"moduleResolution": "bundler",
|
|
29
|
+
"jsx": "react-jsx",
|
|
30
|
+
"jsxImportSource": "@emberkit/core",
|
|
31
|
+
"strict": true,
|
|
32
|
+
"esModuleInterop": true,
|
|
33
|
+
"skipLibCheck": true,
|
|
34
|
+
"forceConsistentCasingInFileNames": true,
|
|
35
|
+
"resolveJsonModule": true,
|
|
36
|
+
"isolatedModules": true,
|
|
37
|
+
"noEmit": true,
|
|
38
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
39
|
+
"paths": {
|
|
40
|
+
"@/*": ["./src/*"]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"include": ["src"],
|
|
44
|
+
"exclude": ["node_modules", "dist"]
|
|
45
|
+
}`,
|
|
46
|
+
"vite.config.ts": `import { defineConfig } from 'vite';
|
|
47
|
+
import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
|
|
48
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
plugins: [emberkitVitePlugin(), tailwindcss()],
|
|
52
|
+
server: {
|
|
53
|
+
port: 3000,
|
|
54
|
+
host: 'localhost',
|
|
55
|
+
},
|
|
56
|
+
esbuild: {
|
|
57
|
+
jsxImportSource: '@emberkit/core',
|
|
58
|
+
},
|
|
59
|
+
});`,
|
|
60
|
+
"index.html": `<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="UTF-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
65
|
+
<title>{{name}}</title>
|
|
66
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
67
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
68
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
69
|
+
</head>
|
|
70
|
+
<body id="app">
|
|
71
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>`,
|
|
74
|
+
"src/index.tsx": `import { render } from '@emberkit/core';
|
|
75
|
+
import { routes } from 'virtual:emberkit-routes';
|
|
76
|
+
import App from './routes/_layout';
|
|
77
|
+
import './styles.css';
|
|
78
|
+
|
|
79
|
+
const root = document.getElementById('app');
|
|
80
|
+
|
|
81
|
+
if (root) {
|
|
82
|
+
try {
|
|
83
|
+
render(App, root, { routes });
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('[entry] Render error:', error);
|
|
86
|
+
}
|
|
87
|
+
}`,
|
|
88
|
+
"src/styles.css": `@import "tailwindcss";
|
|
89
|
+
|
|
90
|
+
@theme {
|
|
91
|
+
--color-brand-50: #f0fdf4;
|
|
92
|
+
--color-brand-100: #dcfce7;
|
|
93
|
+
--color-brand-200: #bbf7d0;
|
|
94
|
+
--color-brand-300: #86efac;
|
|
95
|
+
--color-brand-400: #4ade80;
|
|
96
|
+
--color-brand-500: #22c55e;
|
|
97
|
+
--color-brand-600: #16a34a;
|
|
98
|
+
--color-brand-700: #15803d;
|
|
99
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
body {
|
|
103
|
+
@apply bg-gray-50 text-gray-900 font-sans;
|
|
104
|
+
}`,
|
|
105
|
+
"src/routes/_layout.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
106
|
+
import { signal } from '@emberkit/core';
|
|
107
|
+
import { Sidebar, Header } from '@emberkit/ui';
|
|
108
|
+
|
|
109
|
+
const Layout: RouteComponent = ({ children }) => {
|
|
110
|
+
const sidebarOpen = signal(true);
|
|
111
|
+
|
|
112
|
+
const sidebarItems = [
|
|
113
|
+
{ label: 'Dashboard', href: '/dashboard', icon: 'grid' },
|
|
114
|
+
{ label: 'Analytics', href: '/analytics', icon: 'chart' },
|
|
115
|
+
{ label: 'Users', href: '/users', icon: 'users' },
|
|
116
|
+
{ label: 'Projects', href: '/projects', icon: 'folder' },
|
|
117
|
+
{ label: 'Settings', href: '/settings', icon: 'settings' },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="min-h-screen flex">
|
|
122
|
+
<Sidebar
|
|
123
|
+
logo={<span className="font-bold text-lg">⚡ {{name}}</span>}
|
|
124
|
+
items={sidebarItems}
|
|
125
|
+
collapsed={!sidebarOpen.value}
|
|
126
|
+
onToggle={() => { sidebarOpen.value = !sidebarOpen.value; }}
|
|
127
|
+
className="w-64"
|
|
128
|
+
/>
|
|
129
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
130
|
+
<Header
|
|
131
|
+
title="Dashboard"
|
|
132
|
+
user={{ name: 'User', avatar: '' }}
|
|
133
|
+
onMenuClick={() => { sidebarOpen.value = !sidebarOpen.value; }}
|
|
134
|
+
/>
|
|
135
|
+
<main className="flex-1 p-6 overflow-auto">
|
|
136
|
+
{children}
|
|
137
|
+
</main>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default Layout;`,
|
|
144
|
+
"src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
145
|
+
import { Card, Badge } from '@emberkit/ui';
|
|
146
|
+
|
|
147
|
+
const DashboardPage: RouteComponent = () => {
|
|
148
|
+
const stats = [
|
|
149
|
+
{ label: 'Total Revenue', value: '$45,231', change: '+20.1%', positive: true },
|
|
150
|
+
{ label: 'Active Users', value: '2,338', change: '+15.3%', positive: true },
|
|
151
|
+
{ label: 'New Signups', value: '1,247', change: '+8.2%', positive: true },
|
|
152
|
+
{ label: 'Churn Rate', value: '2.4%', change: '-0.5%', positive: true },
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
const recentActivity = [
|
|
156
|
+
{ user: 'John Doe', action: 'created a new project', time: '2 minutes ago' },
|
|
157
|
+
{ user: 'Jane Smith', action: 'upgraded to Pro plan', time: '15 minutes ago' },
|
|
158
|
+
{ user: 'Mike Johnson', action: 'invited 3 team members', time: '1 hour ago' },
|
|
159
|
+
{ user: 'Sarah Williams', action: 'published a new post', time: '2 hours ago' },
|
|
160
|
+
{ user: 'Alex Brown', action: 'deleted an old project', time: '3 hours ago' },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="space-y-6">
|
|
165
|
+
<div>
|
|
166
|
+
<h1 className="text-2xl font-bold">Welcome back</h1>
|
|
167
|
+
<p className="text-gray-600 mt-1">Here's what's happening with your account today.</p>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Stats Grid */}
|
|
171
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
172
|
+
{stats.map((stat) => (
|
|
173
|
+
<Card key={stat.label} padding="lg">
|
|
174
|
+
<p className="text-sm text-gray-600">{stat.label}</p>
|
|
175
|
+
<p className="text-2xl font-bold mt-1">{stat.value}</p>
|
|
176
|
+
<Badge variant={stat.positive ? 'success' : 'danger'} size="sm" className="mt-2">
|
|
177
|
+
{stat.change}
|
|
178
|
+
</Badge>
|
|
179
|
+
</Card>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div className="grid lg:grid-cols-2 gap-6">
|
|
184
|
+
{/* Chart Placeholder */}
|
|
185
|
+
<Card padding="lg">
|
|
186
|
+
<h3 className="font-semibold mb-4">Revenue Overview</h3>
|
|
187
|
+
<div className="h-64 bg-gray-100 rounded-lg flex items-center justify-center text-gray-500">
|
|
188
|
+
Chart placeholder - integrate your preferred charting library
|
|
189
|
+
</div>
|
|
190
|
+
</Card>
|
|
191
|
+
|
|
192
|
+
{/* Recent Activity */}
|
|
193
|
+
<Card padding="lg">
|
|
194
|
+
<h3 className="font-semibold mb-4">Recent Activity</h3>
|
|
195
|
+
<div className="space-y-4">
|
|
196
|
+
{recentActivity.map((item, i) => (
|
|
197
|
+
<div key={i} className="flex items-start gap-3 pb-4 border-b border-gray-100 last:border-0 last:pb-0">
|
|
198
|
+
<div className="w-8 h-8 rounded-full bg-brand-100 flex items-center justify-center text-brand-700 font-medium text-sm">
|
|
199
|
+
{item.user.charAt(0)}
|
|
200
|
+
</div>
|
|
201
|
+
<div className="flex-1 min-w-0">
|
|
202
|
+
<p className="text-sm">
|
|
203
|
+
<span className="font-medium">{item.user}</span>{' '}
|
|
204
|
+
{item.action}
|
|
205
|
+
</p>
|
|
206
|
+
<p className="text-xs text-gray-500 mt-0.5">{item.time}</p>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
</Card>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Quick Actions */}
|
|
215
|
+
<Card padding="lg">
|
|
216
|
+
<h3 className="font-semibold mb-4">Quick Actions</h3>
|
|
217
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
218
|
+
{[
|
|
219
|
+
{ label: 'New Project', icon: '+' },
|
|
220
|
+
{ label: 'Invite User', icon: '👤' },
|
|
221
|
+
{ label: 'View Reports', icon: '📊' },
|
|
222
|
+
{ label: 'Settings', icon: '⚙' },
|
|
223
|
+
].map((action) => (
|
|
224
|
+
<button
|
|
225
|
+
key={action.label}
|
|
226
|
+
className="p-4 rounded-lg border border-gray-200 hover:border-brand-300 hover:bg-brand-50 transition-colors text-center"
|
|
227
|
+
>
|
|
228
|
+
<span className="text-2xl block mb-2">{action.icon}</span>
|
|
229
|
+
<span className="text-sm font-medium">{action.label}</span>
|
|
230
|
+
</button>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
</Card>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export default DashboardPage;`,
|
|
239
|
+
"src/routes/users.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
240
|
+
import { Card, Badge, Button, Input } from '@emberkit/ui';
|
|
241
|
+
import { signal } from '@emberkit/core';
|
|
242
|
+
|
|
243
|
+
interface User {
|
|
244
|
+
id: number;
|
|
245
|
+
name: string;
|
|
246
|
+
email: string;
|
|
247
|
+
role: string;
|
|
248
|
+
status: 'active' | 'inactive' | 'pending';
|
|
249
|
+
lastActive: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const users: User[] = [
|
|
253
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'active', lastActive: '2 min ago' },
|
|
254
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'Editor', status: 'active', lastActive: '1 hour ago' },
|
|
255
|
+
{ id: 3, name: 'Mike Johnson', email: 'mike@example.com', role: 'Viewer', status: 'inactive', lastActive: '3 days ago' },
|
|
256
|
+
{ id: 4, name: 'Sarah Williams', email: 'sarah@example.com', role: 'Editor', status: 'active', lastActive: '5 min ago' },
|
|
257
|
+
{ id: 5, name: 'Alex Brown', email: 'alex@example.com', role: 'Viewer', status: 'pending', lastActive: 'Never' },
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const UsersPage: RouteComponent = () => {
|
|
261
|
+
const search = signal('');
|
|
262
|
+
|
|
263
|
+
const filteredUsers = users.filter(
|
|
264
|
+
(u) =>
|
|
265
|
+
u.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
|
266
|
+
u.email.toLowerCase().includes(search.value.toLowerCase())
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const statusVariant = (status: User['status']) => {
|
|
270
|
+
switch (status) {
|
|
271
|
+
case 'active': return 'success';
|
|
272
|
+
case 'inactive': return 'default';
|
|
273
|
+
case 'pending': return 'warning';
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div className="space-y-6">
|
|
279
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
280
|
+
<div>
|
|
281
|
+
<h1 className="text-2xl font-bold">Users</h1>
|
|
282
|
+
<p className="text-gray-600 mt-1">Manage your team members and their roles.</p>
|
|
283
|
+
</div>
|
|
284
|
+
<Button variant="primary">Add User</Button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<Card padding="lg">
|
|
288
|
+
<div className="mb-4">
|
|
289
|
+
<Input
|
|
290
|
+
placeholder="Search users..."
|
|
291
|
+
value={search.value}
|
|
292
|
+
onInput={(e) => { search.value = e.currentTarget.value; }}
|
|
293
|
+
className="max-w-sm"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
<div className="overflow-x-auto">
|
|
297
|
+
<table className="w-full text-sm">
|
|
298
|
+
<thead>
|
|
299
|
+
<tr className="border-b border-gray-200">
|
|
300
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Name</th>
|
|
301
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Role</th>
|
|
302
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Status</th>
|
|
303
|
+
<th className="text-left py-3 px-4 font-medium text-gray-600">Last Active</th>
|
|
304
|
+
<th className="text-right py-3 px-4 font-medium text-gray-600">Actions</th>
|
|
305
|
+
</tr>
|
|
306
|
+
</thead>
|
|
307
|
+
<tbody>
|
|
308
|
+
{filteredUsers.map((user) => (
|
|
309
|
+
<tr key={user.id} className="border-b border-gray-100 hover:bg-gray-50">
|
|
310
|
+
<td className="py-3 px-4">
|
|
311
|
+
<div>
|
|
312
|
+
<p className="font-medium">{user.name}</p>
|
|
313
|
+
<p className="text-gray-500 text-xs">{user.email}</p>
|
|
314
|
+
</div>
|
|
315
|
+
</td>
|
|
316
|
+
<td className="py-3 px-4">{user.role}</td>
|
|
317
|
+
<td className="py-3 px-4">
|
|
318
|
+
<Badge variant={statusVariant(user.status)} size="sm">
|
|
319
|
+
{user.status}
|
|
320
|
+
</Badge>
|
|
321
|
+
</td>
|
|
322
|
+
<td className="py-3 px-4 text-gray-500">{user.lastActive}</td>
|
|
323
|
+
<td className="py-3 px-4 text-right">
|
|
324
|
+
<button className="text-brand-600 hover:underline text-sm">Edit</button>
|
|
325
|
+
</td>
|
|
326
|
+
</tr>
|
|
327
|
+
))}
|
|
328
|
+
</tbody>
|
|
329
|
+
</table>
|
|
330
|
+
</div>
|
|
331
|
+
</Card>
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default UsersPage;`,
|
|
337
|
+
"src/routes/settings.tsx": `import type { RouteComponent } from '@emberkit/core';
|
|
338
|
+
import { Card, Input, Button, Alert } from '@emberkit/ui';
|
|
339
|
+
import { signal } from '@emberkit/core';
|
|
340
|
+
|
|
341
|
+
const SettingsPage: RouteComponent = () => {
|
|
342
|
+
const name = signal('John Doe');
|
|
343
|
+
const email = signal('john@example.com');
|
|
344
|
+
const saved = signal(false);
|
|
345
|
+
|
|
346
|
+
const handleSave = (e: Event) => {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
saved.value = true;
|
|
349
|
+
setTimeout(() => { saved.value = false; }, 3000);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div className="space-y-6 max-w-2xl">
|
|
354
|
+
<div>
|
|
355
|
+
<h1 className="text-2xl font-bold">Settings</h1>
|
|
356
|
+
<p className="text-gray-600 mt-1">Manage your account preferences.</p>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
{saved.value && (
|
|
360
|
+
<Alert variant="success">Settings saved successfully!</Alert>
|
|
361
|
+
)}
|
|
362
|
+
|
|
363
|
+
<Card padding="lg">
|
|
364
|
+
<h3 className="font-semibold mb-6">Profile</h3>
|
|
365
|
+
<form onSubmit={handleSave} className="space-y-4">
|
|
366
|
+
<Input
|
|
367
|
+
label="Full Name"
|
|
368
|
+
value={name.value}
|
|
369
|
+
onInput={(e) => { name.value = e.currentTarget.value; }}
|
|
370
|
+
/>
|
|
371
|
+
<Input
|
|
372
|
+
label="Email"
|
|
373
|
+
type="email"
|
|
374
|
+
value={email.value}
|
|
375
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
376
|
+
/>
|
|
377
|
+
<div className="flex justify-end">
|
|
378
|
+
<Button variant="primary" type="submit">Save Changes</Button>
|
|
379
|
+
</div>
|
|
380
|
+
</form>
|
|
381
|
+
</Card>
|
|
382
|
+
|
|
383
|
+
<Card padding="lg">
|
|
384
|
+
<h3 className="font-semibold mb-6">Password</h3>
|
|
385
|
+
<form className="space-y-4">
|
|
386
|
+
<Input label="Current Password" type="password" />
|
|
387
|
+
<Input label="New Password" type="password" />
|
|
388
|
+
<Input label="Confirm Password" type="password" />
|
|
389
|
+
<div className="flex justify-end">
|
|
390
|
+
<Button variant="primary">Update Password</Button>
|
|
391
|
+
</div>
|
|
392
|
+
</form>
|
|
393
|
+
</Card>
|
|
394
|
+
|
|
395
|
+
<Card padding="lg" className="border-red-200">
|
|
396
|
+
<h3 className="font-semibold mb-2 text-red-600">Danger Zone</h3>
|
|
397
|
+
<p className="text-sm text-gray-600 mb-4">
|
|
398
|
+
Once you delete your account, there is no going back.
|
|
399
|
+
</p>
|
|
400
|
+
<Button variant="danger">Delete Account</Button>
|
|
401
|
+
</Card>
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export default SettingsPage;`,
|
|
407
|
+
};
|
package/dist/templates/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export const routeTemplate = `import type { RouteComponent } from '@emberkit/
|
|
1
|
+
export const routeTemplate = `import type { RouteComponent } from '@emberkit/core';
|
|
2
2
|
|
|
3
|
-
const {{name}}
|
|
3
|
+
const {{name}}: RouteComponent = () => {
|
|
4
4
|
return (
|
|
5
5
|
<div>
|
|
6
6
|
<h1>{{name}}</h1>
|
|
@@ -8,15 +8,13 @@ const {{name}}Route: RouteComponent = (props) => {
|
|
|
8
8
|
);
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export default {{name}}
|
|
11
|
+
export default {{name}};
|
|
12
12
|
`;
|
|
13
|
-
export const componentTemplate = `
|
|
14
|
-
|
|
15
|
-
interface {{name}}Props {
|
|
13
|
+
export const componentTemplate = `interface {{name}}Props {
|
|
16
14
|
className?: string;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
const {{name}}
|
|
17
|
+
const {{name}} = ({ className = '' }: {{name}}Props) => {
|
|
20
18
|
return (
|
|
21
19
|
<div className={className}>
|
|
22
20
|
{{name}} component
|
|
@@ -26,15 +24,11 @@ const {{name}}: Component<{{name}}Props> = ({ className = '' }) => {
|
|
|
26
24
|
|
|
27
25
|
export default {{name}};
|
|
28
26
|
`;
|
|
29
|
-
export const layoutTemplate = `import type { RouteComponent } from '@emberkit/
|
|
30
|
-
|
|
31
|
-
interface {{name}}LayoutProps {
|
|
32
|
-
children: JSX.Element;
|
|
33
|
-
}
|
|
27
|
+
export const layoutTemplate = `import type { RouteComponent } from '@emberkit/core';
|
|
34
28
|
|
|
35
|
-
const {{name}}Layout: RouteComponent
|
|
29
|
+
const {{name}}Layout: RouteComponent = ({ children }) => {
|
|
36
30
|
return (
|
|
37
|
-
<div
|
|
31
|
+
<div>
|
|
38
32
|
<header>
|
|
39
33
|
<nav>{{name}} Navigation</nav>
|
|
40
34
|
</header>
|
|
@@ -46,10 +40,13 @@ const {{name}}Layout: RouteComponent<{{name}}LayoutProps> = ({ children }) => {
|
|
|
46
40
|
|
|
47
41
|
export default {{name}}Layout;
|
|
48
42
|
`;
|
|
49
|
-
export const errorBoundaryTemplate = `import type { RouteComponent } from '@emberkit/
|
|
50
|
-
|
|
43
|
+
export const errorBoundaryTemplate = `import type { RouteComponent } from '@emberkit/core';
|
|
44
|
+
|
|
45
|
+
interface {{name}}ErrorProps {
|
|
46
|
+
error: Error;
|
|
47
|
+
}
|
|
51
48
|
|
|
52
|
-
const {{name}}Error: RouteComponent<{
|
|
49
|
+
const {{name}}Error: RouteComponent<{{name}}ErrorProps> = ({ error }) => {
|
|
53
50
|
return (
|
|
54
51
|
<div className="error-boundary">
|
|
55
52
|
<h2>Something went wrong</h2>
|
|
@@ -60,21 +57,52 @@ const {{name}}Error: RouteComponent<{ error: Error }> = ({ error }) => {
|
|
|
60
57
|
|
|
61
58
|
export default {{name}}Error;
|
|
62
59
|
`;
|
|
63
|
-
export const loaderTemplate = `import type {
|
|
60
|
+
export const loaderTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
|
|
64
61
|
|
|
65
|
-
export const loader:
|
|
62
|
+
export const loader: LoaderFunction = async ({ params, query, request }) => {
|
|
66
63
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
data: {
|
|
65
|
+
// Add your data here
|
|
66
|
+
},
|
|
67
|
+
} as LoaderResult<unknown>;
|
|
68
|
+
};
|
|
69
|
+
`;
|
|
70
|
+
export const actionTemplate = `import type { ActionFunction, LoaderResult } from '@emberkit/core';
|
|
71
|
+
|
|
72
|
+
export const action: ActionFunction = async ({ params, request }) => {
|
|
73
|
+
const formData = await request.formData();
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
data: {
|
|
77
|
+
success: true,
|
|
78
|
+
},
|
|
79
|
+
} as LoaderResult<unknown>;
|
|
80
|
+
};
|
|
81
|
+
`;
|
|
82
|
+
export const apiRouteTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
|
|
83
|
+
|
|
84
|
+
export const GET: LoaderFunction = async ({ params, query, request }) => {
|
|
85
|
+
return {
|
|
86
|
+
data: {
|
|
87
|
+
message: 'Hello from API',
|
|
88
|
+
},
|
|
89
|
+
} as LoaderResult<unknown>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const POST: LoaderFunction = async ({ request }) => {
|
|
93
|
+
const body = await request.json();
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
data: {
|
|
97
|
+
received: body,
|
|
98
|
+
},
|
|
99
|
+
} as LoaderResult<unknown>;
|
|
69
100
|
};
|
|
70
101
|
`;
|
|
71
102
|
export const configTemplate = `import { defineConfig } from '@emberkit/core';
|
|
72
103
|
|
|
73
104
|
export default defineConfig({
|
|
74
|
-
mode: '
|
|
75
|
-
routes: {
|
|
76
|
-
output: 'src/generated/routes.ts',
|
|
77
|
-
},
|
|
105
|
+
mode: 'hybrid',
|
|
78
106
|
server: {
|
|
79
107
|
port: 3000,
|
|
80
108
|
},
|
|
@@ -84,27 +112,134 @@ export default defineConfig({
|
|
|
84
112
|
},
|
|
85
113
|
});
|
|
86
114
|
`;
|
|
87
|
-
export const indexTemplate = `import { render } from '@emberkit/
|
|
115
|
+
export const indexTemplate = `import { render } from '@emberkit/core';
|
|
116
|
+
import { routes } from 'virtual:emberkit-routes';
|
|
88
117
|
import App from './routes/_layout';
|
|
118
|
+
import './styles.css';
|
|
89
119
|
|
|
90
120
|
const root = document.getElementById('app');
|
|
91
121
|
|
|
92
122
|
if (root) {
|
|
93
|
-
|
|
123
|
+
try {
|
|
124
|
+
render(App, root, { routes });
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('[entry] Render error:', error);
|
|
127
|
+
}
|
|
94
128
|
}
|
|
95
129
|
`;
|
|
96
|
-
export const layoutRoutesTemplate =
|
|
130
|
+
export const layoutRoutesTemplate = `// EmberKit uses file-based routing.
|
|
131
|
+
// Routes are automatically discovered from the src/routes directory.
|
|
132
|
+
// - src/routes/index.tsx → /
|
|
133
|
+
// - src/routes/about.tsx → /about
|
|
134
|
+
// - src/routes/[slug].tsx → /:slug
|
|
135
|
+
// - src/routes/[...rest].tsx → catch-all
|
|
97
136
|
|
|
98
|
-
export
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
export {};
|
|
138
|
+
`;
|
|
139
|
+
export const signalTemplate = `import { signal, computed, effect } from '@emberkit/core';
|
|
140
|
+
|
|
141
|
+
// Writable signal
|
|
142
|
+
const count = signal(0);
|
|
143
|
+
|
|
144
|
+
// Computed value
|
|
145
|
+
const doubled = computed(() => count.value * 2);
|
|
146
|
+
|
|
147
|
+
// Side effect
|
|
148
|
+
effect(() => {
|
|
149
|
+
console.log('Count changed to:', count.value);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Update
|
|
153
|
+
count.value++;
|
|
154
|
+
|
|
155
|
+
// Batch updates
|
|
156
|
+
import { batch } from '@emberkit/core';
|
|
157
|
+
|
|
158
|
+
batch(() => {
|
|
159
|
+
count.value = 10;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export { count, doubled };
|
|
163
|
+
`;
|
|
164
|
+
export const contextTemplate = `import { createContext, useContext } from '@emberkit/core';
|
|
165
|
+
|
|
166
|
+
interface {{name}}Context {
|
|
167
|
+
// Define your context shape
|
|
168
|
+
value: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const {{name}}Context = createContext<{{name}}Context>({
|
|
172
|
+
value: 'default',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Provider usage:
|
|
176
|
+
// <{{name}}Context.Provider value={{ value: 'hello' }}>
|
|
177
|
+
// {children}
|
|
178
|
+
// </{{name}}Context.Provider>
|
|
179
|
+
|
|
180
|
+
// Consumer usage:
|
|
181
|
+
// const ctx = useContext({{name}}Context);
|
|
182
|
+
|
|
183
|
+
export { {{name}}Context };
|
|
184
|
+
`;
|
|
185
|
+
export const formTemplate = `import { signal } from '@emberkit/core';
|
|
186
|
+
|
|
187
|
+
const {{name}}Form = () => {
|
|
188
|
+
const email = signal('');
|
|
189
|
+
const password = signal('');
|
|
190
|
+
const error = signal<string | null>(null);
|
|
191
|
+
const loading = signal(false);
|
|
192
|
+
|
|
193
|
+
const handleSubmit = async (e: Event) => {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
error.value = null;
|
|
196
|
+
loading.value = true;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const response = await fetch('/api/auth/login', {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: { 'Content-Type': 'application/json' },
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
email: email.value,
|
|
204
|
+
password: password.value,
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
throw new Error('Login failed');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Handle success
|
|
213
|
+
} catch (err) {
|
|
214
|
+
error.value = err instanceof Error ? err.message : 'Unknown error';
|
|
215
|
+
} finally {
|
|
216
|
+
loading.value = false;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<form onSubmit={handleSubmit}>
|
|
222
|
+
<input
|
|
223
|
+
type="email"
|
|
224
|
+
value={email.value}
|
|
225
|
+
onInput={(e) => { email.value = e.currentTarget.value; }}
|
|
226
|
+
placeholder="Email"
|
|
227
|
+
/>
|
|
228
|
+
<input
|
|
229
|
+
type="password"
|
|
230
|
+
value={password.value}
|
|
231
|
+
onInput={(e) => { password.value = e.currentTarget.value; }}
|
|
232
|
+
placeholder="Password"
|
|
233
|
+
/>
|
|
234
|
+
{error.value && <p className="text-red-500">{error.value}</p>}
|
|
235
|
+
<button type="submit" disabled={loading.value}>
|
|
236
|
+
{loading.value ? 'Loading...' : 'Submit'}
|
|
237
|
+
</button>
|
|
238
|
+
</form>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export default {{name}}Form;
|
|
108
243
|
`;
|
|
109
244
|
export function getTemplate(type) {
|
|
110
245
|
const templates = {
|
|
@@ -113,7 +248,13 @@ export function getTemplate(type) {
|
|
|
113
248
|
layout: layoutTemplate,
|
|
114
249
|
error: errorBoundaryTemplate,
|
|
115
250
|
loader: loaderTemplate,
|
|
251
|
+
action: actionTemplate,
|
|
252
|
+
api: apiRouteTemplate,
|
|
116
253
|
config: configTemplate,
|
|
254
|
+
index: indexTemplate,
|
|
255
|
+
signal: signalTemplate,
|
|
256
|
+
context: contextTemplate,
|
|
257
|
+
form: formTemplate,
|
|
117
258
|
};
|
|
118
259
|
return templates[type] ?? routeTemplate;
|
|
119
260
|
}
|