@emberkit/cli 0.5.2 → 0.6.1-alpha.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.
Files changed (47) hide show
  1. package/dist/cli.js +44 -10
  2. package/dist/commands/create.js +10 -21
  3. package/dist/templates/action/index.js +12 -0
  4. package/dist/templates/action.js +12 -0
  5. package/dist/templates/api/api.js +280 -0
  6. package/dist/templates/apiRoute/index.js +20 -0
  7. package/dist/templates/apiRoute.js +20 -0
  8. package/dist/templates/blog/blog.js +358 -0
  9. package/dist/templates/component/index.js +14 -0
  10. package/dist/templates/component.js +14 -0
  11. package/dist/templates/config/index.js +13 -0
  12. package/dist/templates/config.js +13 -0
  13. package/dist/templates/context/index.js +21 -0
  14. package/dist/templates/context.js +21 -0
  15. package/dist/templates/dashboard/dashboard.js +406 -0
  16. package/dist/templates/entry/index.js +15 -0
  17. package/dist/templates/entry.js +15 -0
  18. package/dist/templates/errorBoundary/index.js +17 -0
  19. package/dist/templates/errorBoundary.js +17 -0
  20. package/dist/templates/form/index.js +59 -0
  21. package/dist/templates/form.js +59 -0
  22. package/dist/templates/index.js +0 -260
  23. package/dist/templates/layout/index.js +16 -0
  24. package/dist/templates/layout.js +16 -0
  25. package/dist/templates/layoutRoutes/index.js +9 -0
  26. package/dist/templates/layoutRoutes.js +9 -0
  27. package/dist/templates/loader/index.js +10 -0
  28. package/dist/templates/loader.js +10 -0
  29. package/dist/templates/minimal/minimal.js +89 -0
  30. package/dist/templates/project-templates/_shared/base.js +139 -0
  31. package/dist/templates/project-templates/api/api.js +209 -0
  32. package/dist/templates/project-templates/blog/blog.js +283 -0
  33. package/dist/templates/project-templates/dashboard/dashboard.js +331 -0
  34. package/dist/templates/project-templates/minimal/minimal.js +21 -0
  35. package/dist/templates/project-templates/saas/saas.js +461 -0
  36. package/dist/templates/project-templates/starter-kit/starter.js +209 -0
  37. package/dist/templates/project-templates/starter-kit/with-ui.js +365 -0
  38. package/dist/templates/projects/starter.js +286 -0
  39. package/dist/templates/projects/with-ui.js +445 -0
  40. package/dist/templates/projects.js +419 -191
  41. package/dist/templates/route/index.js +12 -0
  42. package/dist/templates/route.js +12 -0
  43. package/dist/templates/saas/saas.js +539 -0
  44. package/dist/templates/signal/index.js +25 -0
  45. package/dist/templates/signal.js +25 -0
  46. package/dist/utils/generator.js +30 -2
  47. package/package.json +1 -2
package/dist/cli.js CHANGED
@@ -2,8 +2,9 @@ import inquirer from "inquirer";
2
2
  import { dev } from "./commands/dev.js";
3
3
  import { build } from "./commands/build.js";
4
4
  import { preview } from "./commands/preview.js";
5
- import { create } from "./commands/create.js";
6
- import { TEMPLATES } from "./commands/create.js";
5
+ import { create, TEMPLATES } from "./commands/create.js";
6
+ import { generate } from "./utils/generator.js";
7
+ import { toKebabCase } from "./templates/index.js";
7
8
  export async function runCLI(args) {
8
9
  const [command, ...restArgs] = args.slice(2);
9
10
  if (!command) {
@@ -28,7 +29,7 @@ export async function runCLI(args) {
28
29
  break;
29
30
  case "--version":
30
31
  case "-v":
31
- console.log("EmberKit CLI v0.1.0");
32
+ console.log("EmberKit CLI v0.6.0");
32
33
  break;
33
34
  case "--help":
34
35
  case "-h":
@@ -42,7 +43,7 @@ export async function runCLI(args) {
42
43
  }
43
44
  function showHelp() {
44
45
  console.log(`
45
- 🔥 EmberKit CLI v0.1.0
46
+ 🔥 EmberKit CLI v0.6.0
46
47
 
47
48
  Usage: emberkit <command> [options]
48
49
 
@@ -51,15 +52,24 @@ Commands:
51
52
  dev Start development server
52
53
  build Build for production
53
54
  preview Preview production build
54
- generate <type> Generate code (routes, components, etc.)
55
+ generate <type> <name> Generate a file from a template
55
56
 
56
57
  Options:
57
58
  --template, -t <id> Project template to use
58
59
  --no-install Skip dependency installation
60
+ --path, -p <path> Output path for generate (overrides default)
59
61
  --help, -h Show this help message
60
62
  --version, -v Show version number
61
63
 
62
- Templates:
64
+ Generate types:
65
+ route Route component (src/routes/)
66
+ component UI component (src/components/)
67
+ layout Layout component (src/layouts/)
68
+ loader Data loader (src/loaders/)
69
+ action Form action (src/actions/)
70
+ api API route handler (src/routes/_api/)
71
+
72
+ Project templates:
63
73
  basic Simple starter with Tailwind CSS (default)
64
74
  with-ui Starter with EmberKit UI components
65
75
  minimal Barebones project, no CSS framework
@@ -163,12 +173,36 @@ function extractFlagValue(args, longFlag, shortFlag) {
163
173
  }
164
174
  return undefined;
165
175
  }
176
+ const GENERATE_TYPES = {
177
+ route: { dir: "src/routes", ext: ".tsx" },
178
+ component: { dir: "src/components", ext: ".tsx" },
179
+ layout: { dir: "src/layouts", ext: ".tsx" },
180
+ error: { dir: "src/routes", ext: ".tsx" },
181
+ loader: { dir: "src/loaders", ext: ".ts" },
182
+ action: { dir: "src/actions", ext: ".ts" },
183
+ api: { dir: "src/routes/_api", ext: ".ts" },
184
+ };
166
185
  async function runGenerate(args) {
167
- const [type, name] = args;
186
+ const nonFlagArgs = args.filter((a) => !a.startsWith("-"));
187
+ const [type, name] = nonFlagArgs;
168
188
  if (!type || !name) {
169
- console.error("Usage: emberkit generate <type> <name>");
189
+ console.error(`Usage: emberkit generate <type> <name> [--path <file-path>]\n\nTypes: ${Object.keys(GENERATE_TYPES).join(", ")}`);
190
+ process.exit(1);
191
+ }
192
+ const explicitPath = extractFlagValue(args, "--path", "-p");
193
+ const typeConfig = GENERATE_TYPES[type];
194
+ if (!typeConfig && !explicitPath) {
195
+ console.error(`Unknown type "${type}". Valid types: ${Object.keys(GENERATE_TYPES).join(", ")}`);
196
+ process.exit(1);
197
+ }
198
+ const filePath = explicitPath ??
199
+ `${typeConfig.dir}/${toKebabCase(name)}${typeConfig.ext}`;
200
+ const result = await generate({ name, path: filePath, template: type });
201
+ if (result.success) {
202
+ console.log(`✓ Generated ${type}: ${result.path}`);
203
+ }
204
+ else {
205
+ console.error(`✗ ${result.error}`);
170
206
  process.exit(1);
171
207
  }
172
- console.log(`🎨 Generating ${type}: ${name}`);
173
- console.log("(Not yet implemented)");
174
208
  }
@@ -1,13 +1,15 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "fs";
2
- import { resolve, join } from "path";
2
+ import { resolve, join, dirname } 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
+ import { formatTemplate, toKebabCase } from "../templates/index.js";
6
+ import { starterFiles } from "../templates/project-templates/starter-kit/starter.js";
7
+ import { withUiTemplate } from "../templates/project-templates/starter-kit/with-ui.js";
8
+ import { minimalTemplate } from "../templates/project-templates/minimal/minimal.js";
9
+ import { blogTemplate } from "../templates/project-templates/blog/blog.js";
10
+ import { saasTemplate } from "../templates/project-templates/saas/saas.js";
11
+ import { dashboardTemplate } from "../templates/project-templates/dashboard/dashboard.js";
12
+ import { apiTemplate } from "../templates/project-templates/api/api.js";
11
13
  const RESET = "\x1b[0m";
12
14
  const BOLD = "\x1b[1m";
13
15
  const DIM = "\x1b[2m";
@@ -67,19 +69,6 @@ function printInfo(message) {
67
69
  const info = BRIGHT_BLUE + "›" + RESET;
68
70
  console.log(` ${info} ${DIM + message + RESET}`);
69
71
  }
70
- function formatTemplate(template, vars) {
71
- let result = template;
72
- for (const [key, value] of Object.entries(vars)) {
73
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
74
- }
75
- return result;
76
- }
77
- function toKebabCase(str) {
78
- return str
79
- .replace(/([a-z])([A-Z])/g, "$1-$2")
80
- .replace(/[\s_]+/g, "-")
81
- .toLowerCase();
82
- }
83
72
  function getNpmPackageName(name) {
84
73
  const kebab = toKebabCase(name);
85
74
  return kebab.startsWith("@") ? kebab : kebab.replace(/^emberkit-/, "");
@@ -115,7 +104,7 @@ export async function create(options) {
115
104
  const templateFiles = template.files;
116
105
  for (const [filePath, content] of Object.entries(templateFiles)) {
117
106
  const fullPath = join(targetDir, filePath);
118
- const dir = join(targetDir, filePath.split("/").slice(0, -1).join("/"));
107
+ const dir = dirname(fullPath);
119
108
  if (!existsSync(dir)) {
120
109
  mkdirSync(dir, { recursive: true });
121
110
  }
@@ -0,0 +1,12 @@
1
+ export const actionTemplate = `import type { ActionFunction, LoaderResult } from '@emberkit/core';
2
+
3
+ export const action: ActionFunction = async ({ params, request }) => {
4
+ const formData = await request.formData();
5
+
6
+ return {
7
+ data: {
8
+ success: true,
9
+ },
10
+ } as LoaderResult<unknown>;
11
+ };
12
+ `;
@@ -0,0 +1,12 @@
1
+ export const actionTemplate = `import type { ActionFunction, LoaderResult } from '@emberkit/core';
2
+
3
+ export const action: ActionFunction = async ({ params, request }) => {
4
+ const formData = await request.formData();
5
+
6
+ return {
7
+ data: {
8
+ success: true,
9
+ },
10
+ } as LoaderResult<unknown>;
11
+ };
12
+ `;
@@ -0,0 +1,280 @@
1
+ export const apiTemplate = {
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
+ },
15
+ "devDependencies": {
16
+ "@emberkit/cli": "^0.2.4",
17
+ "typescript": "^5.7.0",
18
+ "vite": "^6.0.0"
19
+ }
20
+ }`,
21
+ "tsconfig.json": `{
22
+ "compilerOptions": {
23
+ "target": "ES2022",
24
+ "module": "ESNext",
25
+ "moduleResolution": "bundler",
26
+ "jsx": "react-jsx",
27
+ "jsxImportSource": "@emberkit/core",
28
+ "strict": true,
29
+ "esModuleInterop": true,
30
+ "skipLibCheck": true,
31
+ "forceConsistentCasingInFileNames": true,
32
+ "resolveJsonModule": true,
33
+ "isolatedModules": true,
34
+ "noEmit": true,
35
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
36
+ "paths": {
37
+ "@/*": ["./src/*"]
38
+ }
39
+ },
40
+ "include": ["src"],
41
+ "exclude": ["node_modules", "dist"]
42
+ }`,
43
+ "vite.config.ts": `import { defineConfig } from 'vite';
44
+ import { emberkitVitePlugin } from '@emberkit/core/vite-plugin';
45
+
46
+ export default defineConfig({
47
+ plugins: [emberkitVitePlugin()],
48
+ server: {
49
+ port: 3000,
50
+ host: 'localhost',
51
+ },
52
+ esbuild: {
53
+ jsxImportSource: '@emberkit/core',
54
+ },
55
+ });`,
56
+ "index.html": `<!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="UTF-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
61
+ <title>{{name}} API</title>
62
+ </head>
63
+ <body id="app">
64
+ <script type="module" src="/src/index.tsx"></script>
65
+ </body>
66
+ </html>`,
67
+ "src/index.tsx": `import { render } from '@emberkit/core';
68
+ import { routes } from 'virtual:emberkit-routes';
69
+ import App from './routes/index';
70
+
71
+ const root = document.getElementById('app');
72
+
73
+ if (root) {
74
+ try {
75
+ render(App, root, { routes });
76
+ } catch (error) {
77
+ console.error('[entry] Render error:', error);
78
+ }
79
+ }`,
80
+ "src/routes/index.tsx": `import type { RouteComponent } from '@emberkit/core';
81
+
82
+ const ApiHome: RouteComponent = () => {
83
+ const endpoints = [
84
+ { method: 'GET', path: '/api/users', desc: 'List all users' },
85
+ { method: 'POST', path: '/api/users', desc: 'Create a new user' },
86
+ { method: 'GET', path: '/api/users/:id', desc: 'Get a user by ID' },
87
+ { method: 'PUT', path: '/api/users/:id', desc: 'Update a user' },
88
+ { method: 'DELETE', path: '/api/users/:id', desc: 'Delete a user' },
89
+ { method: 'GET', path: '/api/health', desc: 'Health check' },
90
+ ];
91
+
92
+ const methodColor = (method: string) => {
93
+ switch (method) {
94
+ case 'GET': return 'bg-green-100 text-green-700';
95
+ case 'POST': return 'bg-blue-100 text-blue-700';
96
+ case 'PUT': return 'bg-yellow-100 text-yellow-700';
97
+ case 'DELETE': return 'bg-red-100 text-red-700';
98
+ default: return 'bg-gray-100 text-gray-700';
99
+ }
100
+ };
101
+
102
+ return (
103
+ <div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: '800px', margin: '2rem auto', padding: '0 1rem' }}>
104
+ <header className="mb-12">
105
+ <h1 className="text-3xl font-bold mb-2">{{name}} API</h1>
106
+ <p className="text-gray-600">RESTful API built with EmberKit</p>
107
+ <div className="mt-4 p-4 bg-gray-100 rounded-lg">
108
+ <code className="text-sm">Base URL: http://localhost:3000</code>
109
+ </div>
110
+ </header>
111
+
112
+ <section>
113
+ <h2 className="text-xl font-semibold mb-4">Endpoints</h2>
114
+ <div className="space-y-3">
115
+ {endpoints.map((ep) => (
116
+ <div key={ep.method + ep.path} className="flex items-center gap-4 p-4 border border-gray-200 rounded-lg">
117
+ <span className={\`px-2 py-1 rounded text-xs font-bold \${methodColor(ep.method)}\`}>
118
+ {ep.method}
119
+ </span>
120
+ <code className="text-sm font-mono">{ep.path}</code>
121
+ <span className="text-gray-500 text-sm ml-auto">{ep.desc}</span>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ </section>
126
+
127
+ <section className="mt-12">
128
+ <h2 className="text-xl font-semibold mb-4">Example Response</h2>
129
+ <pre className="p-4 bg-gray-900 text-gray-100 rounded-lg overflow-x-auto text-sm">
130
+ {JSON.stringify({
131
+ data: [
132
+ { id: 1, name: 'John Doe', email: 'john@example.com' },
133
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
134
+ ],
135
+ meta: { page: 1, total: 2 },
136
+ }, null, 2)}
137
+ </pre>
138
+ </section>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ export default ApiHome;`,
144
+ "src/routes/_api/users.ts": `import type { LoaderFunction, LoaderResult, ActionFunction } from '@emberkit/core';
145
+
146
+ // In-memory store (replace with database in production)
147
+ const users = [
148
+ { id: 1, name: 'John Doe', email: 'john@example.com', createdAt: '2026-01-15' },
149
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', createdAt: '2026-02-20' },
150
+ { id: 3, name: 'Mike Johnson', email: 'mike@example.com', createdAt: '2026-03-10' },
151
+ ];
152
+
153
+ let nextId = 4;
154
+
155
+ export const GET: LoaderFunction = async ({ query }) => {
156
+ const page = parseInt(query.page as string) || 1;
157
+ const limit = parseInt(query.limit as string) || 10;
158
+ const start = (page - 1) * limit;
159
+ const end = start + limit;
160
+
161
+ return {
162
+ data: {
163
+ users: users.slice(start, end),
164
+ meta: {
165
+ page,
166
+ limit,
167
+ total: users.length,
168
+ },
169
+ },
170
+ } as LoaderResult<unknown>;
171
+ };
172
+
173
+ export const POST: ActionFunction = async ({ request }) => {
174
+ const body = await request.json();
175
+
176
+ if (!body.name || !body.email) {
177
+ return {
178
+ error: {
179
+ code: 'VALIDATION_ERROR',
180
+ message: 'Name and email are required',
181
+ status: 400,
182
+ },
183
+ } as LoaderResult<unknown>;
184
+ }
185
+
186
+ const newUser = {
187
+ id: nextId++,
188
+ name: body.name,
189
+ email: body.email,
190
+ createdAt: new Date().toISOString().split('T')[0],
191
+ };
192
+
193
+ users.push(newUser);
194
+
195
+ return {
196
+ data: newUser,
197
+ } as LoaderResult<unknown>;
198
+ };`,
199
+ "src/routes/_api/users/[id].ts": `import type { LoaderFunction, LoaderResult, ActionFunction } from '@emberkit/core';
200
+
201
+ // In-memory store (replace with database in production)
202
+ const users = [
203
+ { id: 1, name: 'John Doe', email: 'john@example.com', createdAt: '2026-01-15' },
204
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', createdAt: '2026-02-20' },
205
+ { id: 3, name: 'Mike Johnson', email: 'mike@example.com', createdAt: '2026-03-10' },
206
+ ];
207
+
208
+ export const GET: LoaderFunction = async ({ params }) => {
209
+ const id = parseInt(params.id);
210
+ const user = users.find((u) => u.id === id);
211
+
212
+ if (!user) {
213
+ return {
214
+ error: {
215
+ code: 'NOT_FOUND',
216
+ message: 'User not found',
217
+ status: 404,
218
+ },
219
+ } as LoaderResult<unknown>;
220
+ }
221
+
222
+ return {
223
+ data: user,
224
+ } as LoaderResult<unknown>;
225
+ };
226
+
227
+ export const PUT: ActionFunction = async ({ params, request }) => {
228
+ const id = parseInt(params.id);
229
+ const index = users.findIndex((u) => u.id === id);
230
+
231
+ if (index === -1) {
232
+ return {
233
+ error: {
234
+ code: 'NOT_FOUND',
235
+ message: 'User not found',
236
+ status: 404,
237
+ },
238
+ } as LoaderResult<unknown>;
239
+ }
240
+
241
+ const body = await request.json();
242
+ users[index] = { ...users[index], ...body };
243
+
244
+ return {
245
+ data: users[index],
246
+ } as LoaderResult<unknown>;
247
+ };
248
+
249
+ export const DELETE: ActionFunction = async ({ params }) => {
250
+ const id = parseInt(params.id);
251
+ const index = users.findIndex((u) => u.id === id);
252
+
253
+ if (index === -1) {
254
+ return {
255
+ error: {
256
+ code: 'NOT_FOUND',
257
+ message: 'User not found',
258
+ status: 404,
259
+ },
260
+ } as LoaderResult<unknown>;
261
+ }
262
+
263
+ users.splice(index, 1);
264
+
265
+ return {
266
+ data: { success: true },
267
+ } as LoaderResult<unknown>;
268
+ };`,
269
+ "src/routes/_api/health.ts": `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
270
+
271
+ export const GET: LoaderFunction = async () => {
272
+ return {
273
+ data: {
274
+ status: 'ok',
275
+ timestamp: new Date().toISOString(),
276
+ uptime: process.uptime(),
277
+ },
278
+ } as LoaderResult<unknown>;
279
+ };`,
280
+ };
@@ -0,0 +1,20 @@
1
+ export const apiRouteTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
2
+
3
+ export const GET: LoaderFunction = async ({ params, query, request }) => {
4
+ return {
5
+ data: {
6
+ message: 'Hello from API',
7
+ },
8
+ } as LoaderResult<unknown>;
9
+ };
10
+
11
+ export const POST: LoaderFunction = async ({ request }) => {
12
+ const body = await request.json();
13
+
14
+ return {
15
+ data: {
16
+ received: body,
17
+ },
18
+ } as LoaderResult<unknown>;
19
+ };
20
+ `;
@@ -0,0 +1,20 @@
1
+ export const apiRouteTemplate = `import type { LoaderFunction, LoaderResult } from '@emberkit/core';
2
+
3
+ export const GET: LoaderFunction = async ({ params, query, request }) => {
4
+ return {
5
+ data: {
6
+ message: 'Hello from API',
7
+ },
8
+ } as LoaderResult<unknown>;
9
+ };
10
+
11
+ export const POST: LoaderFunction = async ({ request }) => {
12
+ const body = await request.json();
13
+
14
+ return {
15
+ data: {
16
+ received: body,
17
+ },
18
+ } as LoaderResult<unknown>;
19
+ };
20
+ `;