ebade 0.3.0 → 0.4.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/CHANGELOG.md +5 -0
- package/README.md +30 -30
- package/ROADMAP.md +17 -12
- package/cli/scaffold.js +315 -183
- package/cli/simulate.js +102 -0
- package/cli/templates/feature-grid.tsx +80 -0
- package/cli/templates/footer.tsx +121 -0
- package/cli/templates/hero-section.tsx +34 -0
- package/cli/templates/login-form.tsx +124 -0
- package/cli/templates/navbar.tsx +53 -0
- package/cli/templates/pricing-table.tsx +140 -0
- package/cli/templates/signup-form.tsx +111 -0
- package/demo.tape +2 -2
- package/examples/saas-dashboard.ebade.yaml +2 -0
- package/netlify.toml +7 -0
- package/package.json +1 -1
- package/packages/mcp-server/README.md +3 -3
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/index.ts +12 -16
- package/packages/mcp-server/src/tools/scaffold.ts +153 -404
- package/packages/vscode-extension/README.md +11 -8
- package/packages/vscode-extension/ebade-0.3.0.vsix +0 -0
- package/packages/vscode-extension/ebade-0.3.1.vsix +0 -0
- package/packages/vscode-extension/ebade-0.3.2.vsix +0 -0
- package/packages/vscode-extension/images/icon.png +0 -0
- package/packages/vscode-extension/package.json +2 -1
- package/packages/vscode-extension/snippets/ebade.json +86 -0
- package/www/README.md +36 -0
- package/www/app/favicon.ico +0 -0
- package/{landing/style.css → www/app/globals.css} +390 -19
- package/www/app/layout.tsx +66 -0
- package/www/app/page.tsx +374 -0
- package/www/app/playground/page.tsx +627 -0
- package/www/components/ThreeCanvas.tsx +156 -0
- package/www/next.config.ts +19 -0
- package/www/package-lock.json +1779 -0
- package/www/package.json +27 -0
- package/www/postcss.config.mjs +7 -0
- package/www/public/logo.png +0 -0
- package/www/tsconfig.json +42 -0
- package/landing/index.html +0 -268
- package/landing/main.js +0 -147
- package/packages/vscode-extension/images/icon.svg +0 -6
- /package/{demo.gif → assets/demo.gif} +0 -0
- /package/{demo.mp4 → assets/demo.mp4} +0 -0
- /package/{landing → www/public}/_headers +0 -0
- /package/{landing → www/public}/favicon.svg +0 -0
- /package/{landing → www/public}/og-image.png +0 -0
- /package/{landing → www/public}/og-readme.png +0 -0
package/cli/scaffold.js
CHANGED
|
@@ -62,60 +62,86 @@ function parseEbade(ebadePath) {
|
|
|
62
62
|
// ============================================
|
|
63
63
|
// Component Generator Templates
|
|
64
64
|
// ============================================
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<button className="hero-cta" style={{ backgroundColor: '${
|
|
74
|
-
design.colors?.primary || "#6366f1"
|
|
75
|
-
}' }}>
|
|
76
|
-
Shop Now
|
|
77
|
-
</button>
|
|
78
|
-
</div>
|
|
79
|
-
</section>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
`,
|
|
83
|
-
"product-grid": (design) => `
|
|
84
|
-
export function ProductGrid({ products }) {
|
|
85
|
-
return (
|
|
86
|
-
<div className="product-grid">
|
|
87
|
-
{products.map((product) => (
|
|
88
|
-
<ProductCard key={product.id} product={product} />
|
|
89
|
-
))}
|
|
90
|
-
</div>
|
|
65
|
+
// ============================================
|
|
66
|
+
// Template Resolver
|
|
67
|
+
// ============================================
|
|
68
|
+
function getComponentTemplate(componentName, design) {
|
|
69
|
+
const templatePath = path.join(
|
|
70
|
+
process.cwd(),
|
|
71
|
+
"cli/templates",
|
|
72
|
+
`${componentName}.tsx`
|
|
91
73
|
);
|
|
92
|
-
}
|
|
93
|
-
`,
|
|
94
|
-
"add-to-cart": (design) => `
|
|
95
|
-
import { useState } from 'react';
|
|
96
74
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
75
|
+
if (fs.existsSync(templatePath)) {
|
|
76
|
+
let content = fs.readFileSync(templatePath, "utf-8");
|
|
77
|
+
|
|
78
|
+
// Config-based replacement (e.g., {{primary}})
|
|
79
|
+
const primaryColor = design?.colors?.primary || "#6366f1";
|
|
80
|
+
content = content.replace(/\{\{primary\}\}/g, primaryColor);
|
|
81
|
+
|
|
82
|
+
return content;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback to placeholder if template file doesn't exist
|
|
86
|
+
return `import React from 'react';
|
|
87
|
+
import { cn } from "@/lib/utils";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 🧠 Generated via ebade
|
|
91
|
+
* Component: ${toPascalCase(componentName)}
|
|
92
|
+
* Status: Placeholder (No template found in cli/templates)
|
|
93
|
+
*/
|
|
94
|
+
export function ${toPascalCase(componentName)}() {
|
|
100
95
|
return (
|
|
101
|
-
<div className="
|
|
102
|
-
<div className="
|
|
103
|
-
<
|
|
104
|
-
<span>{quantity}</span>
|
|
105
|
-
<button onClick={() => setQuantity(q => q + 1)}>+</button>
|
|
96
|
+
<div className="p-12 border-2 border-dashed border-border rounded-3xl text-center bg-muted/30">
|
|
97
|
+
<div className="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
98
|
+
<span className="text-2xl">🧩</span>
|
|
106
99
|
</div>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
>
|
|
112
|
-
Add to Cart
|
|
113
|
-
</button>
|
|
100
|
+
<h3 className="text-xl font-bold mb-2">${toPascalCase(componentName)}</h3>
|
|
101
|
+
<p className="text-sm text-muted-foreground max-w-xs mx-auto">
|
|
102
|
+
No template found for this intent. Create a file at <code>cli/templates/${componentName}.tsx</code> to customize.
|
|
103
|
+
</p>
|
|
114
104
|
</div>
|
|
115
105
|
);
|
|
116
106
|
}
|
|
117
|
-
|
|
118
|
-
}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function generateComponentTest(componentName) {
|
|
111
|
+
const name = toPascalCase(componentName);
|
|
112
|
+
return `import { describe, it, expect } from 'vitest';
|
|
113
|
+
import { render } from '@testing-library/react';
|
|
114
|
+
import { ${name} } from './${componentName}';
|
|
115
|
+
import React from 'react';
|
|
116
|
+
|
|
117
|
+
describe('${name} Component', () => {
|
|
118
|
+
it('renders without crashing', () => {
|
|
119
|
+
render(<${name} />);
|
|
120
|
+
expect(document.body).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function generateVitestConfig() {
|
|
127
|
+
return `import { defineConfig } from 'vitest/config';
|
|
128
|
+
import react from '@vitejs/plugin-react';
|
|
129
|
+
import path from 'path';
|
|
130
|
+
|
|
131
|
+
export default defineConfig({
|
|
132
|
+
plugins: [react()],
|
|
133
|
+
test: {
|
|
134
|
+
environment: 'jsdom',
|
|
135
|
+
globals: true,
|
|
136
|
+
},
|
|
137
|
+
resolve: {
|
|
138
|
+
alias: {
|
|
139
|
+
'@': path.resolve(__dirname, './'),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
119
145
|
|
|
120
146
|
// ============================================
|
|
121
147
|
// Page Generator
|
|
@@ -127,10 +153,13 @@ function generatePage(page, design) {
|
|
|
127
153
|
.join("\n") || "";
|
|
128
154
|
|
|
129
155
|
const componentUsage =
|
|
130
|
-
page.components
|
|
131
|
-
|
|
156
|
+
page.components
|
|
157
|
+
?.map((c) => ` <${toPascalCase(c)} />`)
|
|
158
|
+
.join("\n") || " {/* No components defined */}";
|
|
159
|
+
|
|
160
|
+
return `import React from 'react';
|
|
161
|
+
${componentImports}
|
|
132
162
|
|
|
133
|
-
return `
|
|
134
163
|
/**
|
|
135
164
|
* 🧠 Generated via ebade - The Agent-First Framework
|
|
136
165
|
* https://github.com/hasankemaldemirci/ebade
|
|
@@ -138,19 +167,26 @@ function generatePage(page, design) {
|
|
|
138
167
|
* @page('${page.path}')
|
|
139
168
|
* @intent('${page.intent}')
|
|
140
169
|
*/
|
|
141
|
-
|
|
142
|
-
${componentImports}
|
|
143
|
-
|
|
144
|
-
|
|
145
170
|
export default function ${toPascalCase(page.intent)}Page() {
|
|
146
171
|
return (
|
|
147
|
-
<
|
|
172
|
+
<div className="min-h-screen bg-slate-950 text-white">
|
|
173
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
|
|
174
|
+
<header className="mb-12">
|
|
175
|
+
<h1 className="text-3xl font-bold tracking-tight opacity-90">${toPascalCase(
|
|
176
|
+
page.intent
|
|
177
|
+
)}</h1>
|
|
178
|
+
<p className="text-sm opacity-40 mt-1">Route: ${page.path}</p>
|
|
179
|
+
</header>
|
|
180
|
+
|
|
181
|
+
<main className="space-y-12">
|
|
148
182
|
${componentUsage}
|
|
149
|
-
|
|
183
|
+
</main>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
150
186
|
);
|
|
151
187
|
}
|
|
152
188
|
|
|
153
|
-
// Auth
|
|
189
|
+
// Auth: ${page.auth || "public"}
|
|
154
190
|
`;
|
|
155
191
|
}
|
|
156
192
|
|
|
@@ -221,16 +257,30 @@ function generatePackageJson(config) {
|
|
|
221
257
|
build: "next build",
|
|
222
258
|
start: "next start",
|
|
223
259
|
lint: "next lint",
|
|
260
|
+
test: "vitest",
|
|
224
261
|
},
|
|
225
262
|
dependencies: {
|
|
226
263
|
next: "^14.0.0",
|
|
227
264
|
react: "^18.2.0",
|
|
228
265
|
"react-dom": "^18.2.0",
|
|
266
|
+
"lucide-react": "^0.300.0",
|
|
267
|
+
clsx: "^2.1.0",
|
|
268
|
+
"tailwind-merge": "^2.2.0",
|
|
269
|
+
"class-variance-authority": "^0.7.0",
|
|
270
|
+
"framer-motion": "^11.0.0",
|
|
229
271
|
},
|
|
230
272
|
devDependencies: {
|
|
231
273
|
"@types/node": "^20.0.0",
|
|
232
274
|
"@types/react": "^18.2.0",
|
|
233
275
|
"@types/react-dom": "^18.2.0",
|
|
276
|
+
"@testing-library/react": "^14.1.2",
|
|
277
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
278
|
+
jsdom: "^22.1.0",
|
|
279
|
+
vitest: "^0.34.6",
|
|
280
|
+
autoprefixer: "^10.0.1",
|
|
281
|
+
postcss: "^8.4.0",
|
|
282
|
+
tailwindcss: "^3.4.0",
|
|
283
|
+
"tailwindcss-animate": "^1.0.7",
|
|
234
284
|
typescript: "^5.0.0",
|
|
235
285
|
},
|
|
236
286
|
},
|
|
@@ -239,6 +289,87 @@ function generatePackageJson(config) {
|
|
|
239
289
|
);
|
|
240
290
|
}
|
|
241
291
|
|
|
292
|
+
function generateTailwindConfig() {
|
|
293
|
+
return `/** @type {import('tailwindcss').Config} */
|
|
294
|
+
module.exports = {
|
|
295
|
+
darkMode: ["class"],
|
|
296
|
+
content: [
|
|
297
|
+
'./pages/**/*.{ts,tsx}',
|
|
298
|
+
'./components/**/*.{ts,tsx}',
|
|
299
|
+
'./app/**/*.{ts,tsx}',
|
|
300
|
+
'./src/**/*.{ts,tsx}',
|
|
301
|
+
],
|
|
302
|
+
prefix: "",
|
|
303
|
+
theme: {
|
|
304
|
+
container: {
|
|
305
|
+
center: true,
|
|
306
|
+
padding: "2rem",
|
|
307
|
+
screens: {
|
|
308
|
+
"2xl": "1400px",
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
extend: {
|
|
312
|
+
colors: {
|
|
313
|
+
border: "hsl(var(--border))",
|
|
314
|
+
input: "hsl(var(--input))",
|
|
315
|
+
ring: "hsl(var(--ring))",
|
|
316
|
+
background: "hsl(var(--background))",
|
|
317
|
+
foreground: "hsl(var(--foreground))",
|
|
318
|
+
primary: {
|
|
319
|
+
DEFAULT: "hsl(var(--primary))",
|
|
320
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
321
|
+
},
|
|
322
|
+
secondary: {
|
|
323
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
324
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
325
|
+
},
|
|
326
|
+
destructive: {
|
|
327
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
328
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
329
|
+
},
|
|
330
|
+
muted: {
|
|
331
|
+
DEFAULT: "hsl(var(--muted))",
|
|
332
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
333
|
+
},
|
|
334
|
+
accent: {
|
|
335
|
+
DEFAULT: "hsl(var(--accent))",
|
|
336
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
337
|
+
},
|
|
338
|
+
popover: {
|
|
339
|
+
DEFAULT: "hsl(var(--popover))",
|
|
340
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
341
|
+
},
|
|
342
|
+
card: {
|
|
343
|
+
DEFAULT: "hsl(var(--card))",
|
|
344
|
+
foreground: "hsl(var(--card-foreground))",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
borderRadius: {
|
|
348
|
+
lg: "var(--radius)",
|
|
349
|
+
md: "calc(var(--radius) - 2px)",
|
|
350
|
+
sm: "calc(var(--radius) - 4px)",
|
|
351
|
+
},
|
|
352
|
+
keyframes: {
|
|
353
|
+
"accordion-down": {
|
|
354
|
+
from: { height: "0" },
|
|
355
|
+
to: { height: "var(--radix-accordion-content-height)" },
|
|
356
|
+
},
|
|
357
|
+
"accordion-up": {
|
|
358
|
+
from: { height: "var(--radix-accordion-content-height)" },
|
|
359
|
+
to: { height: "0" },
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
animation: {
|
|
363
|
+
"accordion-down": "accordion-down 0.2s ease-out",
|
|
364
|
+
"accordion-up": "accordion-up 0.2s ease-out",
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
plugins: [require("tailwindcss-animate")],
|
|
369
|
+
}
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
|
|
242
373
|
function generateNextConfig() {
|
|
243
374
|
return `/** @type {import('next').NextConfig} */
|
|
244
375
|
const nextConfig = {};
|
|
@@ -250,6 +381,7 @@ function generateTsConfig() {
|
|
|
250
381
|
return JSON.stringify(
|
|
251
382
|
{
|
|
252
383
|
compilerOptions: {
|
|
384
|
+
target: "es5",
|
|
253
385
|
lib: ["dom", "dom.iterable", "esnext"],
|
|
254
386
|
allowJs: true,
|
|
255
387
|
skipLibCheck: true,
|
|
@@ -257,10 +389,10 @@ function generateTsConfig() {
|
|
|
257
389
|
noEmit: true,
|
|
258
390
|
esModuleInterop: true,
|
|
259
391
|
module: "esnext",
|
|
260
|
-
moduleResolution: "
|
|
392
|
+
moduleResolution: "node",
|
|
261
393
|
resolveJsonModule: true,
|
|
262
394
|
isolatedModules: true,
|
|
263
|
-
jsx: "
|
|
395
|
+
jsx: "react-jsx",
|
|
264
396
|
incremental: true,
|
|
265
397
|
plugins: [{ name: "next" }],
|
|
266
398
|
paths: { "@/*": ["./*"] },
|
|
@@ -275,7 +407,8 @@ function generateTsConfig() {
|
|
|
275
407
|
|
|
276
408
|
function generateLayout(config) {
|
|
277
409
|
const fontFamily = config.design?.font || "Inter";
|
|
278
|
-
return `import
|
|
410
|
+
return `import React from 'react';
|
|
411
|
+
import type { Metadata } from "next";
|
|
279
412
|
import "./globals.css";
|
|
280
413
|
|
|
281
414
|
export const metadata: Metadata = {
|
|
@@ -307,119 +440,95 @@ export default function RootLayout({
|
|
|
307
440
|
}
|
|
308
441
|
|
|
309
442
|
function generateGlobalsCss(design) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
background-color: var(--color-primary);
|
|
381
|
-
color: white;
|
|
382
|
-
padding: 0.75rem 1.5rem;
|
|
383
|
-
border-radius: var(--radius-lg);
|
|
384
|
-
border: none;
|
|
385
|
-
cursor: pointer;
|
|
386
|
-
transition: opacity 0.2s;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
.btn-primary:hover {
|
|
390
|
-
opacity: 0.9;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.product-grid {
|
|
394
|
-
display: grid;
|
|
395
|
-
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
396
|
-
gap: 1.5rem;
|
|
397
|
-
padding: 2rem;
|
|
443
|
+
const primary = design.colors?.primary || "#6366f1";
|
|
444
|
+
|
|
445
|
+
// Helper to convert hex to HSL for CSS vars if needed
|
|
446
|
+
// For now we'll use standard shadcn slate
|
|
447
|
+
return `@tailwind base;
|
|
448
|
+
@tailwind components;
|
|
449
|
+
@tailwind utilities;
|
|
450
|
+
|
|
451
|
+
@layer base {
|
|
452
|
+
:root {
|
|
453
|
+
--background: 0 0% 100%;
|
|
454
|
+
--foreground: 222.2 84% 4.9%;
|
|
455
|
+
|
|
456
|
+
--card: 0 0% 100%;
|
|
457
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
458
|
+
|
|
459
|
+
--popover: 0 0% 100%;
|
|
460
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
461
|
+
|
|
462
|
+
--primary: 221.2 83.2% 53.3%;
|
|
463
|
+
--primary-foreground: 210 40% 98%;
|
|
464
|
+
|
|
465
|
+
--secondary: 210 40% 96.1%;
|
|
466
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
467
|
+
|
|
468
|
+
--muted: 210 40% 96.1%;
|
|
469
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
470
|
+
|
|
471
|
+
--accent: 210 40% 96.1%;
|
|
472
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
473
|
+
|
|
474
|
+
--destructive: 0 84.2% 60.2%;
|
|
475
|
+
--destructive-foreground: 210 40% 98%;
|
|
476
|
+
|
|
477
|
+
--border: 214.3 31.8% 91.4%;
|
|
478
|
+
--input: 214.3 31.8% 91.4%;
|
|
479
|
+
--ring: 221.2 83.2% 53.3%;
|
|
480
|
+
|
|
481
|
+
--radius: 0.5rem;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.dark {
|
|
485
|
+
--background: 222.2 84% 4.9%;
|
|
486
|
+
--foreground: 210 40% 98%;
|
|
487
|
+
|
|
488
|
+
--card: 222.2 84% 4.9%;
|
|
489
|
+
--card-foreground: 210 40% 98%;
|
|
490
|
+
|
|
491
|
+
--popover: 222.2 84% 4.9%;
|
|
492
|
+
--popover-foreground: 210 40% 98%;
|
|
493
|
+
|
|
494
|
+
--primary: 217.2 91.2% 59.8%;
|
|
495
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
496
|
+
|
|
497
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
498
|
+
--secondary-foreground: 210 40% 98%;
|
|
499
|
+
|
|
500
|
+
--muted: 217.2 32.6% 17.5%;
|
|
501
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
502
|
+
|
|
503
|
+
--accent: 217.2 32.6% 17.5%;
|
|
504
|
+
--accent-foreground: 210 40% 98%;
|
|
505
|
+
|
|
506
|
+
--destructive: 0 62.8% 30.6%;
|
|
507
|
+
--destructive-foreground: 210 40% 98%;
|
|
508
|
+
|
|
509
|
+
--border: 217.2 32.6% 17.5%;
|
|
510
|
+
--input: 217.2 32.6% 17.5%;
|
|
511
|
+
--ring: 224.3 76.3% 48%;
|
|
512
|
+
}
|
|
398
513
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
514
|
+
|
|
515
|
+
@layer base {
|
|
516
|
+
* {
|
|
517
|
+
@apply border-border;
|
|
518
|
+
}
|
|
519
|
+
body {
|
|
520
|
+
@apply bg-background text-foreground;
|
|
521
|
+
}
|
|
404
522
|
}
|
|
405
|
-
|
|
406
|
-
.quantity-selector {
|
|
407
|
-
display: flex;
|
|
408
|
-
align-items: center;
|
|
409
|
-
gap: 0.5rem;
|
|
523
|
+
`;
|
|
410
524
|
}
|
|
411
525
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
border: 1px solid #e5e7eb;
|
|
416
|
-
background: white;
|
|
417
|
-
border-radius: 4px;
|
|
418
|
-
cursor: pointer;
|
|
419
|
-
}
|
|
526
|
+
function generateUtils() {
|
|
527
|
+
return `import { type ClassValue, clsx } from "clsx"
|
|
528
|
+
import { twMerge } from "tailwind-merge"
|
|
420
529
|
|
|
421
|
-
|
|
422
|
-
|
|
530
|
+
export function cn(...inputs: ClassValue[]) {
|
|
531
|
+
return twMerge(clsx(inputs))
|
|
423
532
|
}
|
|
424
533
|
`;
|
|
425
534
|
}
|
|
@@ -550,7 +659,13 @@ function ensureDir(dir) {
|
|
|
550
659
|
// ============================================
|
|
551
660
|
function scaffold(ebadePath, outputDir) {
|
|
552
661
|
const startTime = Date.now();
|
|
553
|
-
|
|
662
|
+
const stats = {
|
|
663
|
+
pages: 0,
|
|
664
|
+
components: 0,
|
|
665
|
+
apiRoutes: 0,
|
|
666
|
+
files: 0,
|
|
667
|
+
tokenSavings: 0,
|
|
668
|
+
};
|
|
554
669
|
|
|
555
670
|
console.log(LOGO);
|
|
556
671
|
|
|
@@ -583,6 +698,10 @@ function scaffold(ebadePath, outputDir) {
|
|
|
583
698
|
log.file(`${dir}/`);
|
|
584
699
|
});
|
|
585
700
|
|
|
701
|
+
// lib/utils.ts
|
|
702
|
+
fs.writeFileSync(path.join(projectDir, "lib/utils.ts"), generateUtils());
|
|
703
|
+
log.file("lib/utils.ts");
|
|
704
|
+
|
|
586
705
|
// ========== Generate Pages ==========
|
|
587
706
|
log.section("Generating pages");
|
|
588
707
|
|
|
@@ -618,21 +737,23 @@ function scaffold(ebadePath, outputDir) {
|
|
|
618
737
|
const spinner2 = ora("Generating components...").start();
|
|
619
738
|
allComponents.forEach((component) => {
|
|
620
739
|
const componentPath = `components/${component}.tsx`;
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
? template(config.design)
|
|
624
|
-
: `// TODO: Implement ${toPascalCase(
|
|
625
|
-
component
|
|
626
|
-
)} component\nexport function ${toPascalCase(
|
|
627
|
-
component
|
|
628
|
-
)}() {\n return <div>${component}</div>;\n}\n`;
|
|
740
|
+
const content = getComponentTemplate(component, config.design);
|
|
741
|
+
stats.tokenSavings += Math.floor(content.length / 4);
|
|
629
742
|
|
|
630
743
|
fs.writeFileSync(path.join(projectDir, componentPath), content.trim());
|
|
744
|
+
|
|
745
|
+
// Generate unit test
|
|
746
|
+
const testPath = `components/${component}.test.tsx`;
|
|
747
|
+
fs.writeFileSync(
|
|
748
|
+
path.join(projectDir, testPath),
|
|
749
|
+
generateComponentTest(component).trim()
|
|
750
|
+
);
|
|
751
|
+
|
|
631
752
|
stats.components++;
|
|
632
|
-
stats.files
|
|
753
|
+
stats.files += 2; // Component + Test
|
|
633
754
|
});
|
|
634
755
|
spinner2.succeed(
|
|
635
|
-
`Generated ${colors.bright}${stats.components}${colors.reset} components`
|
|
756
|
+
`Generated ${colors.bright}${stats.components}${colors.reset} components (+ tests)`
|
|
636
757
|
);
|
|
637
758
|
|
|
638
759
|
// ========== Generate API Routes ==========
|
|
@@ -683,6 +804,20 @@ function scaffold(ebadePath, outputDir) {
|
|
|
683
804
|
fs.writeFileSync(path.join(projectDir, "tsconfig.json"), generateTsConfig());
|
|
684
805
|
log.file("tsconfig.json");
|
|
685
806
|
|
|
807
|
+
// tailwind.config.js
|
|
808
|
+
fs.writeFileSync(
|
|
809
|
+
path.join(projectDir, "tailwind.config.js"),
|
|
810
|
+
generateTailwindConfig()
|
|
811
|
+
);
|
|
812
|
+
log.file("tailwind.config.js");
|
|
813
|
+
|
|
814
|
+
// vitest.config.ts
|
|
815
|
+
fs.writeFileSync(
|
|
816
|
+
path.join(projectDir, "vitest.config.ts"),
|
|
817
|
+
generateVitestConfig()
|
|
818
|
+
);
|
|
819
|
+
log.file("vitest.config.ts");
|
|
820
|
+
|
|
686
821
|
// app/layout.tsx
|
|
687
822
|
fs.writeFileSync(
|
|
688
823
|
path.join(projectDir, "app/layout.tsx"),
|
|
@@ -739,7 +874,6 @@ function scaffold(ebadePath, outputDir) {
|
|
|
739
874
|
|
|
740
875
|
// ========== Summary ==========
|
|
741
876
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
742
|
-
const estimatedTokenSavings = Math.round(stats.files * 35); // ~35 tokens saved per file
|
|
743
877
|
|
|
744
878
|
console.log(`
|
|
745
879
|
${colors.bright}${colors.green} ┌${"─".repeat(41)}┐${colors.reset}
|
|
@@ -759,9 +893,7 @@ ${colors.green} │${colors.reset} ${colors.cyan}📁 Files Created:${
|
|
|
759
893
|
} ${String(stats.files).padEnd(18)} ${colors.green}│${colors.reset}
|
|
760
894
|
${colors.green} │${colors.reset} ${colors.cyan}📊 Token Savings:${
|
|
761
895
|
colors.reset
|
|
762
|
-
} ~${String(
|
|
763
|
-
colors.reset
|
|
764
|
-
}
|
|
896
|
+
} ~${String(stats.tokenSavings).padEnd(17)} ${colors.green}│${colors.reset}
|
|
765
897
|
${colors.green} │${colors.reset} ${colors.cyan}⏱ Completed in:${
|
|
766
898
|
colors.reset
|
|
767
899
|
} ${String(duration + "s").padEnd(18)} ${colors.green}│${colors.reset}
|