ebade 0.4.3 โ†’ 0.4.6

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.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: ebade Prompt-to-Product Workflow
3
+ ---
4
+
5
+ # ๐Ÿฃ ebade Prompt-to-Product Workflow
6
+
7
+ This workflow describes how an AI Agent should use the ebade framework to transform a user's natural language intent into a production-ready codebase.
8
+
9
+ ## 1. Initial Project Scaffolding
10
+ When a user asks to "build", "create", or "start" a new web application:
11
+
12
+ // turbo
13
+ 1. Use the `ebade build` command with the user's prompt.
14
+ ```bash
15
+ node cli/scaffold.js build "[User's Prompt]"
16
+ ```
17
+ *This tool will automatically choose the project type, color palette, and initial components.*
18
+
19
+ 2. Navigate into the generated project directory.
20
+
21
+ ## 2. Understanding the Architecture
22
+ ebade generates a `project.ebade.yaml` file in the project root. This is your **Source of Truth**.
23
+ 1. Read `project.ebade.yaml` to understand the pages, components, and data models.
24
+ 2. Respect the design system tokens in `app/globals.css`.
25
+
26
+ ## 3. Iterative Development
27
+ Once the scaffold is ready, move from "Architect" to "Engineer":
28
+
29
+ 1. **Refine Components**: Locate generated components in `components/`. They are high-quality visual placeholders. Add the specific business logic, API calls, or complex state management requested by the user.
30
+ 2. **Implement API Logic**: Fill the `app/api/` route handlers with real backend logic (database queries, external service integrations).
31
+ 3. **Data Schemas**: Use the generated SQL in `database/schema.sql` to set up the actual database.
32
+
33
+ ## 4. Expanding the Project
34
+ To add new features:
35
+ 1. Update `project.ebade.yaml` with the new intent.
36
+ 2. Run `node cli/scaffold.js scaffold project.ebade.yaml .` to generate the new files while preserving your logic.
37
+ 3. Apply the specific implementation details to the new files.
38
+
39
+ ---
40
+ *Built for the Agent-First Era. ebade empowers you to focus on logic, while we handle the architecture.*
package/CHANGELOG.md CHANGED
@@ -5,6 +5,49 @@ All notable changes to **ebade** will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.6] - 2025-01-10
9
+
10
+ ### Added
11
+
12
+ - **EbadeArchitect (The Brain)**: New core engine that translates natural language prompts into structured `ebade` configurations.
13
+ - **`ebade build <prompt>`**: A "one-shot" project creation command. Designs, scaffolds, and prepares a project in seconds.
14
+ - **Architect Intelligence**: Projects now gain context-aware page structures (E-commerce get cart/products, Blog get posts, etc.) and smarter component selection via Regex.
15
+ - **Auto-Environment**: Automatically generates `.gitignore` and `.env.example` for professional project starts.
16
+ - **Agent Workflow Documentation**: Added `.agent/workflows/prompt-to-product.md` to guide AI agents on using the new tools.
17
+ - **MCP Server v0.4.6**: Integrated `ebade_build` as a tool for otonomous agent usage.
18
+ - **Testing Layer**: Added internal testing suites for both the framework logic and CLI integration.
19
+ - **CLI Branding**: Updated to `v0.4.6` with improved help menus and creative briefs.
20
+
21
+ ### Fixed
22
+
23
+ - Fixed CLI entry point to allow safe imports of `scaffold.js` without triggering help output.
24
+ - Improved project naming logic to filter out filler words (e.g., "Can you make a...").
25
+ - Fixed component integrity check to correctly locate test files in `tests/components/`.
26
+
27
+ ## [0.4.5] - 2025-01-10
28
+ ...
29
+ ## [0.4.5] - 2025-01-10
30
+
31
+ ### Added
32
+
33
+ - UI/UX Overhaul: Premium dark-mode aesthetic with ambient glow and glassmorphism by default.
34
+ - Dynamic Color Support: Real-time Hex-to-HSL conversion to apply user's primary color choice to CSS variables.
35
+ - Component "Intents": Placeholders are now high-quality "Glass Cards" that look like part of a finished UI.
36
+ - Test Organization: All unit tests are now generated in a centralized `tests/` directory instead of being cluttered with components.
37
+
38
+ ### Fixed
39
+
40
+ - API Pathing: Fixed a bug that caused double-nesting (e.g., `/api/api/...`) in generated routes.
41
+ - Removed debug information (headers, route labels) from generated pages for a "turnkey" production feel.
42
+
43
+ ## [0.4.4] - 2025-01-10
44
+
45
+
46
+ ### Fixed
47
+ - Added `postcss.config.js` generation to enable Tailwind styling in scaffolded projects.
48
+ - Fixed double-nesting issues when initializing projects (removed redundant `projectName/projectName` folder creation).
49
+ - Refined Tailwind config to ensure all component paths are correctly watched.
50
+
8
51
  ## [0.4.3] - 2025-01-10
9
52
 
10
53
  ### Fixed
package/cli/scaffold.js CHANGED
@@ -44,7 +44,7 @@ ${colors.magenta} โ–ˆโ–ˆโ•”โ•โ•โ• ${colors.cyan}โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—$
44
44
  ${colors.magenta} โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—${colors.cyan}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•${colors.magenta}โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘${colors.cyan}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•${colors.magenta}โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
45
45
  ${colors.magenta} โ•šโ•โ•โ•โ•โ•โ•โ•${colors.cyan}โ•šโ•โ•โ•โ•โ•โ• ${colors.magenta}โ•šโ•โ• โ•šโ•โ•${colors.cyan}โ•šโ•โ•โ•โ•โ•โ• ${colors.magenta}โ•šโ•โ•โ•โ•โ•โ•โ•${colors.reset}
46
46
 
47
- ${colors.dim}โœจ Agent-First Framework ${colors.yellow}v0.4.3${colors.reset}
47
+ ${colors.dim}โœจ Agent-First Framework ${colors.yellow}v0.4.6${colors.reset}
48
48
  `;
49
49
 
50
50
  const log = {
@@ -58,6 +58,166 @@ const log = {
58
58
  console.log(`\n${colors.bright}${colors.magenta}โ–ธ ${msg}${colors.reset}`),
59
59
  };
60
60
 
61
+ // ============================================
62
+ // ebade Architect (The Brain)
63
+ // ============================================
64
+ export class EbadeArchitect {
65
+ static async plan(prompt) {
66
+ const p = prompt.toLowerCase();
67
+
68
+ // Core App Type Detection
69
+ let type = "saas-dashboard";
70
+ if (p.includes("e-commerce") || p.includes("shop") || p.includes("store"))
71
+ type = "e-commerce";
72
+ if (p.includes("blog") || p.includes("article") || p.includes("news"))
73
+ type = "blog";
74
+ if (p.includes("portfolio") || p.includes("personal") || p.includes("cv"))
75
+ type = "portfolio";
76
+
77
+ // Component Intelligence
78
+ const components = ["navbar", "footer"];
79
+ const features = [];
80
+
81
+ const has = (keyword) => new RegExp(`\\b${keyword}`, "i").test(p);
82
+
83
+ if (has("chart") || has("analytics") || has("graph") || has("metric")) {
84
+ components.push("activity-chart", "stats-grid");
85
+ features.push("Advanced Analytics");
86
+ }
87
+ if (has("saas") || has("dashboard") || has("admin")) {
88
+ components.push("sidebar-navigation", "metrics-cards");
89
+ features.push("Admin Dashboard");
90
+ }
91
+ if (has("login") || has("auth") || has("sign") || has("user")) {
92
+ components.push("login-form", "signup-form");
93
+ features.push("Authentication");
94
+ }
95
+ if (has("price") || has("plan") || has("subscribe") || has("billing")) {
96
+ components.push("pricing-table", "cta-banner");
97
+ features.push("Subscription Tiers");
98
+ }
99
+ if (has("testim") || has("review") || has("social proof"))
100
+ components.push("testimonials-grid");
101
+ if (has("contact") || has("form") || has("help") || has("support"))
102
+ components.push("contact-form");
103
+ if (has("faq") || has("question")) components.push("faq-accordion");
104
+
105
+ // Dynamic Color Palette (Order matters - specific colors should win over vibes)
106
+ let primary = "#6366f1"; // Indigo default
107
+ if (has("gold") || has("luxury") || has("premium") || has("exclusive"))
108
+ primary = "#fbbf24";
109
+ if (has("green") || has("eco") || has("emerald") || has("nature"))
110
+ primary = "#10b981";
111
+ if (
112
+ has("blue") ||
113
+ has("ocean") ||
114
+ has("sky") ||
115
+ has("trust") ||
116
+ has("corp")
117
+ )
118
+ primary = "#3b82f6";
119
+ if (has("violet") || has("purple") || has("creative") || has("design"))
120
+ primary = "#8b5cf6";
121
+ if (has("orange") || has("fire") || has("warm") || has("brand"))
122
+ primary = "#f59e0b";
123
+ if (has("red") || has("danger") || has("hot") || has("love"))
124
+ primary = "#ef4444";
125
+
126
+ // Smart Pages based on Type
127
+ const pages = [
128
+ {
129
+ path: "/",
130
+ intent: "landing-page",
131
+ components: components.filter((c) =>
132
+ [
133
+ "navbar",
134
+ "hero-section",
135
+ "pricing-table",
136
+ "testimonials-grid",
137
+ "cta-banner",
138
+ "footer",
139
+ ].includes(c)
140
+ ),
141
+ },
142
+ ];
143
+
144
+ if (type === "saas-dashboard") {
145
+ pages.push({
146
+ path: "/dashboard",
147
+ intent: "main-dashboard",
148
+ components: components.filter((c) =>
149
+ [
150
+ "sidebar-navigation",
151
+ "stats-grid",
152
+ "activity-chart",
153
+ "metrics-cards",
154
+ ].includes(c)
155
+ ),
156
+ });
157
+ } else if (type === "e-commerce") {
158
+ pages.push(
159
+ {
160
+ path: "/products",
161
+ intent: "product-list",
162
+ components: ["product-grid"],
163
+ },
164
+ { path: "/cart", intent: "shopping-cart", components: ["cart-list"] }
165
+ );
166
+ } else if (type === "blog") {
167
+ pages.push(
168
+ { path: "/posts", intent: "blog-index", components: ["post-list"] },
169
+ {
170
+ path: "/posts/[slug]",
171
+ intent: "blog-post",
172
+ components: ["post-body"],
173
+ }
174
+ );
175
+ }
176
+
177
+ // Build Internal Intent
178
+ const config = {
179
+ name:
180
+ p
181
+ .split(" ")
182
+ .filter(
183
+ (w) =>
184
+ ![
185
+ "can",
186
+ "you",
187
+ "make",
188
+ "create",
189
+ "a",
190
+ "an",
191
+ "the",
192
+ "with",
193
+ "please",
194
+ "super",
195
+ "ultra",
196
+ "is",
197
+ "for",
198
+ "me",
199
+ "my",
200
+ "and",
201
+ ].includes(w.toLowerCase())
202
+ )
203
+ .slice(0, 2)
204
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
205
+ .join("") || "EbadeApp",
206
+ type: type,
207
+ description: prompt,
208
+ features: features.length > 0 ? features : ["Turnkey Scaffold"],
209
+ design: {
210
+ colors: { primary },
211
+ style: "glass-modern",
212
+ },
213
+ pages: pages,
214
+ api: [{ path: "/api/data", methods: ["GET"], intent: "fetch-data" }],
215
+ };
216
+
217
+ return config;
218
+ }
219
+ }
220
+
61
221
  // ============================================
62
222
  // ebade Parser
63
223
  // ============================================
@@ -96,17 +256,19 @@ import { cn } from "@/lib/utils";
96
256
  /**
97
257
  * ๐Ÿง  Generated via ebade
98
258
  * Component: ${toPascalCase(componentName)}
99
- * Status: Placeholder (No template found in cli/templates)
259
+ * Status: Intent needs implementation
100
260
  */
101
261
  export function ${toPascalCase(componentName)}() {
102
262
  return (
103
- <div className="p-12 border-2 border-dashed border-border rounded-3xl text-center bg-muted/30">
104
- <div className="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
105
- <span className="text-2xl">๐Ÿงฉ</span>
263
+ <div className="p-12 glass-card rounded-[2.5rem] text-center min-h-[300px] flex flex-col items-center justify-center group hover:border-primary/50 transition-all">
264
+ <div className="w-20 h-20 bg-primary/10 rounded-[2rem] flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
265
+ <span className="text-3xl">โœจ</span>
106
266
  </div>
107
- <h3 className="text-xl font-bold mb-2">${toPascalCase(componentName)}</h3>
108
- <p className="text-sm text-muted-foreground max-w-xs mx-auto">
109
- No template found for this intent. Create a file at <code>cli/templates/${componentName}.tsx</code> to customize.
267
+ <h3 className="text-2xl font-bold mb-3 text-white">${toPascalCase(
268
+ componentName
269
+ )}</h3>
270
+ <p className="text-slate-400 max-w-sm mx-auto leading-relaxed">
271
+ This intent is defined for your AI agent. To customize, edit <code>components/${componentName}.tsx</code> or use the ebade compiler.
110
272
  </p>
111
273
  </div>
112
274
  );
@@ -176,19 +338,15 @@ ${componentImports}
176
338
  */
177
339
  export default function ${toPascalCase(page.intent)}Page() {
178
340
  return (
179
- <div className="min-h-screen bg-slate-950 text-white">
180
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10">
181
- <header className="mb-12">
182
- <h1 className="text-3xl font-bold tracking-tight opacity-90">${toPascalCase(
183
- page.intent
184
- )}</h1>
185
- <p className="text-sm opacity-40 mt-1">Route: ${page.path}</p>
186
- </header>
341
+ <div className="min-h-screen bg-slate-950 text-white selection:bg-indigo-500/30 selection:text-indigo-200">
342
+ <main className="relative overflow-hidden">
343
+ {/* Ambient background glow */}
344
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-[500px] bg-indigo-600/10 blur-[120px] rounded-full pointer-events-none" />
187
345
 
188
- <main className="space-y-12">
346
+ <div className="relative z-10">
189
347
  ${componentUsage}
190
- </main>
191
- </div>
348
+ </div>
349
+ </main>
192
350
  </div>
193
351
  );
194
352
  }
@@ -384,6 +542,16 @@ export default nextConfig;
384
542
  `;
385
543
  }
386
544
 
545
+ function generatePostcssConfig() {
546
+ return `module.exports = {
547
+ plugins: {
548
+ tailwindcss: {},
549
+ autoprefixer: {},
550
+ },
551
+ }
552
+ `;
553
+ }
554
+
387
555
  function generateTsConfig() {
388
556
  return JSON.stringify(
389
557
  {
@@ -459,65 +627,35 @@ function generateGlobalsCss(design) {
459
627
 
460
628
  @layer base {
461
629
  :root {
462
- --background: 0 0% 100%;
463
- --foreground: 222.2 84% 4.9%;
464
-
465
- --card: 0 0% 100%;
466
- --card-foreground: 222.2 84% 4.9%;
467
-
468
- --popover: 0 0% 100%;
469
- --popover-foreground: 222.2 84% 4.9%;
470
-
471
- --primary: 221.2 83.2% 53.3%;
472
- --primary-foreground: 210 40% 98%;
473
-
474
- --secondary: 210 40% 96.1%;
475
- --secondary-foreground: 222.2 47.4% 11.2%;
476
-
477
- --muted: 210 40% 96.1%;
478
- --muted-foreground: 215.4 16.3% 46.9%;
479
-
480
- --accent: 210 40% 96.1%;
481
- --accent-foreground: 222.2 47.4% 11.2%;
482
-
483
- --destructive: 0 84.2% 60.2%;
484
- --destructive-foreground: 210 40% 98%;
485
-
486
- --border: 214.3 31.8% 91.4%;
487
- --input: 214.3 31.8% 91.4%;
488
- --ring: 221.2 83.2% 53.3%;
489
-
490
- --radius: 0.5rem;
491
- }
630
+ --background: 222 47% 4%;
631
+ --foreground: 213 31% 91%;
492
632
 
493
- .dark {
494
- --background: 222.2 84% 4.9%;
495
- --foreground: 210 40% 98%;
633
+ --card: 222 47% 4%;
634
+ --card-foreground: 213 31% 91%;
496
635
 
497
- --card: 222.2 84% 4.9%;
498
- --card-foreground: 210 40% 98%;
636
+ --popover: 222 47% 4%;
637
+ --popover-foreground: 213 31% 91%;
499
638
 
500
- --popover: 222.2 84% 4.9%;
501
- --popover-foreground: 210 40% 98%;
639
+ --primary: ${hexToHsl(primary)};
640
+ --primary-foreground: 0 0% 100%;
502
641
 
503
- --primary: 217.2 91.2% 59.8%;
504
- --primary-foreground: 222.2 47.4% 11.2%;
642
+ --secondary: 222 47% 11%;
643
+ --secondary-foreground: 213 31% 91%;
505
644
 
506
- --secondary: 217.2 32.6% 17.5%;
507
- --secondary-foreground: 210 40% 98%;
645
+ --muted: 223 47% 11%;
646
+ --muted-foreground: 215.4 16.3% 56.9%;
508
647
 
509
- --muted: 217.2 32.6% 17.5%;
510
- --muted-foreground: 215 20.2% 65.1%;
511
-
512
- --accent: 217.2 32.6% 17.5%;
648
+ --accent: 216 34% 17%;
513
649
  --accent-foreground: 210 40% 98%;
514
650
 
515
- --destructive: 0 62.8% 30.6%;
651
+ --destructive: 0 63% 31%;
516
652
  --destructive-foreground: 210 40% 98%;
517
653
 
518
- --border: 217.2 32.6% 17.5%;
519
- --input: 217.2 32.6% 17.5%;
520
- --ring: 224.3 76.3% 48%;
654
+ --border: 216 34% 17%;
655
+ --input: 216 34% 17%;
656
+ --ring: ${hexToHsl(primary)};
657
+
658
+ --radius: 1rem;
521
659
  }
522
660
  }
523
661
 
@@ -526,9 +664,25 @@ function generateGlobalsCss(design) {
526
664
  @apply border-border;
527
665
  }
528
666
  body {
529
- @apply bg-background text-foreground;
667
+ @apply bg-background text-foreground antialiased;
668
+ font-feature-settings: "cv11", "ss01";
530
669
  }
531
670
  }
671
+
672
+ /* Premium Animations */
673
+ @keyframes float {
674
+ 0% { transform: translateY(0px); }
675
+ 50% { transform: translateY(-10px); }
676
+ 100% { transform: translateY(0px); }
677
+ }
678
+
679
+ .animate-float {
680
+ animation: float 6s ease-in-out infinite;
681
+ }
682
+
683
+ .glass-card {
684
+ @apply bg-white/[0.03] border border-white/10 backdrop-blur-xl;
685
+ }
532
686
  `;
533
687
  }
534
688
 
@@ -542,6 +696,61 @@ export function cn(...inputs: ClassValue[]) {
542
696
  `;
543
697
  }
544
698
 
699
+ function generateGitignore() {
700
+ return `# dependencies
701
+ /node_modules
702
+ /.pnp
703
+ .pnp.js
704
+
705
+ # testing
706
+ /coverage
707
+
708
+ # next.js
709
+ /.next/
710
+ /out/
711
+
712
+ # production
713
+ /build
714
+
715
+ # misc
716
+ .DS_Store
717
+ *.pem
718
+
719
+ # debug
720
+ npm-debug.log*
721
+ yarn-debug.log*
722
+ yarn-error.log*
723
+
724
+ # local env files
725
+ .env*.local
726
+ .env
727
+
728
+ # vercel
729
+ .vercel
730
+
731
+ # typescript
732
+ *.tsbuildinfo
733
+ next-env.d.ts
734
+ `;
735
+ }
736
+
737
+ function generateEnvExample(config) {
738
+ return `# ebade Generated Environment Variables
739
+ # Project: ${config.name}
740
+
741
+ # Database (Supabase / Postgres)
742
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
743
+
744
+ # Authentication (NextAuth / Clerk)
745
+ NEXTAUTH_SECRET="your-secret-here"
746
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=""
747
+
748
+ # API Keys
749
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=""
750
+ STRIPE_SECRET_KEY=""
751
+ `;
752
+ }
753
+
545
754
  // ============================================
546
755
  // Design System CSS Generator
547
756
  // ============================================
@@ -627,20 +836,64 @@ Built with ebade - The Agent-First Framework for the next era of development.
627
836
  // ============================================
628
837
  // Utility Functions
629
838
  // ============================================
630
- function toPascalCase(str) {
839
+ export function toPascalCase(str) {
631
840
  return str
632
- .split("-")
841
+ .split(/[-_]/)
633
842
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
634
843
  .join("");
635
844
  }
636
845
 
637
- function toSnakeCase(str) {
846
+ export function toSnakeCase(str) {
638
847
  return str
639
- .replace(/([A-Z])/g, "_$1")
848
+ .replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
849
+ .replace(/-/g, "_")
640
850
  .toLowerCase()
641
851
  .replace(/^_/, "");
642
852
  }
643
853
 
854
+ export function hexToHsl(hex) {
855
+ let r = 0,
856
+ g = 0,
857
+ b = 0;
858
+ if (hex.length === 4) {
859
+ r = parseInt(hex[1] + hex[1], 16);
860
+ g = parseInt(hex[2] + hex[2], 16);
861
+ b = parseInt(hex[3] + hex[3], 16);
862
+ } else if (hex.length === 7) {
863
+ r = parseInt(hex.slice(1, 3), 16);
864
+ g = parseInt(hex.slice(3, 5), 16);
865
+ b = parseInt(hex.slice(5, 7), 16);
866
+ }
867
+ r /= 255;
868
+ g /= 255;
869
+ b /= 255;
870
+ let max = Math.max(r, g, b),
871
+ min = Math.min(r, g, b);
872
+ let h,
873
+ s,
874
+ l = (max + min) / 2;
875
+ if (max === min) h = s = 0;
876
+ else {
877
+ let d = max - min;
878
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
879
+ switch (max) {
880
+ case r:
881
+ h = (g - b) / d + (g < b ? 6 : 0);
882
+ break;
883
+ case g:
884
+ h = (b - r) / d + 2;
885
+ break;
886
+ case b:
887
+ h = (r - g) / d + 4;
888
+ break;
889
+ }
890
+ h /= 6;
891
+ }
892
+ return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(
893
+ l * 100
894
+ )}%`;
895
+ }
896
+
644
897
  function mapToSqlType(type) {
645
898
  const typeMap = {
646
899
  uuid: "UUID PRIMARY KEY DEFAULT gen_random_uuid()",
@@ -686,7 +939,10 @@ async function scaffold(ebadePath, outputDir) {
686
939
  );
687
940
 
688
941
  // Create output directory structure
689
- const projectDir = path.join(outputDir, config.name);
942
+ // If outputDir already ends with config.name, don't nest again
943
+ const projectDir = outputDir.endsWith(config.name)
944
+ ? outputDir
945
+ : path.join(outputDir, config.name);
690
946
  ensureDir(projectDir);
691
947
 
692
948
  // ========== Generate Structure ==========
@@ -751,8 +1007,11 @@ async function scaffold(ebadePath, outputDir) {
751
1007
 
752
1008
  fs.writeFileSync(path.join(projectDir, componentPath), content.trim());
753
1009
 
754
- // Generate unit test
755
- const testPath = `components/${component}.test.tsx`;
1010
+ // Generate unit test in tests/ directory
1011
+ const testPath = `tests/components/${component}.test.tsx`;
1012
+ const testDir = path.dirname(path.join(projectDir, testPath));
1013
+ ensureDir(testDir);
1014
+
756
1015
  fs.writeFileSync(
757
1016
  path.join(projectDir, testPath),
758
1017
  generateComponentTest(component).trim()
@@ -771,7 +1030,12 @@ async function scaffold(ebadePath, outputDir) {
771
1030
  if (config.api) {
772
1031
  const spinner3 = ora("Generating API routes...").start();
773
1032
  config.api.forEach((endpoint) => {
774
- const apiPath = `app/api${endpoint.path}/route.ts`;
1033
+ // Fix potential /api/api double nesting
1034
+ const cleanPath = endpoint.path.startsWith("/api")
1035
+ ? endpoint.path.slice(4)
1036
+ : endpoint.path;
1037
+
1038
+ const apiPath = `app/api${cleanPath}/route.ts`;
775
1039
  const apiDir = path.dirname(path.join(projectDir, apiPath));
776
1040
  ensureDir(apiDir);
777
1041
 
@@ -820,6 +1084,13 @@ async function scaffold(ebadePath, outputDir) {
820
1084
  );
821
1085
  log.file("tailwind.config.js");
822
1086
 
1087
+ // postcss.config.js
1088
+ fs.writeFileSync(
1089
+ path.join(projectDir, "postcss.config.js"),
1090
+ generatePostcssConfig()
1091
+ );
1092
+ log.file("postcss.config.js");
1093
+
823
1094
  // vitest.config.ts
824
1095
  fs.writeFileSync(
825
1096
  path.join(projectDir, "vitest.config.ts"),
@@ -827,6 +1098,17 @@ async function scaffold(ebadePath, outputDir) {
827
1098
  );
828
1099
  log.file("vitest.config.ts");
829
1100
 
1101
+ // .gitignore
1102
+ fs.writeFileSync(path.join(projectDir, ".gitignore"), generateGitignore());
1103
+ log.file(".gitignore");
1104
+
1105
+ // .env.example
1106
+ fs.writeFileSync(
1107
+ path.join(projectDir, ".env.example"),
1108
+ generateEnvExample(config)
1109
+ );
1110
+ log.file(".env.example");
1111
+
830
1112
  // app/layout.tsx
831
1113
  fs.writeFileSync(
832
1114
  path.join(projectDir, "app/layout.tsx"),
@@ -990,13 +1272,14 @@ async function verifyOutput(projectDir, config) {
990
1272
  }
991
1273
 
992
1274
  // 3. Test Coverage Check
993
- // Ensure every component has a matching test file
1275
+ // Ensure every component has a matching test file in tests/components/
994
1276
  const components = fs
995
1277
  .readdirSync(path.join(projectDir, "components"))
996
1278
  .filter((f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx"));
997
1279
  components.forEach((comp) => {
998
1280
  const testFile = comp.replace(".tsx", ".test.tsx");
999
- if (!fs.existsSync(path.join(projectDir, "components", testFile))) {
1281
+ const testPath = path.join(projectDir, "tests/components", testFile);
1282
+ if (!fs.existsSync(testPath)) {
1000
1283
  results.tests = false;
1001
1284
  results.issues.push(`Missing test for component: ${comp}`);
1002
1285
  }
@@ -1058,12 +1341,14 @@ ${colors.dim}Usage:${colors.reset}
1058
1341
 
1059
1342
  ${colors.dim}Commands:${colors.reset}
1060
1343
  init Create a new ebade project interactively
1344
+ build <prompt> Generate and scaffold a project from a natural language prompt
1061
1345
  scaffold <file> [output] Scaffold a project from ebade file
1062
1346
  dev <file> [output] Watch ebade file and re-scaffold on changes
1063
1347
  playground Open the ebade playground
1064
1348
 
1065
1349
  ${colors.dim}Examples:${colors.reset}
1066
1350
  npx ebade init
1351
+ npx ebade build "A violet themed crypto dashboard"
1067
1352
  npx ebade scaffold examples/saas-dashboard.ebade.yaml ./output
1068
1353
  npx ebade dev my-project.ebade.yaml ./my-app
1069
1354
 
@@ -1227,80 +1512,134 @@ ${LOGO}
1227
1512
  // ============================================
1228
1513
  // Command Router
1229
1514
  // ============================================
1230
- if (
1231
- args.length === 0 ||
1232
- command === "help" ||
1233
- command === "--help" ||
1234
- command === "-h"
1235
- ) {
1236
- showHelp();
1237
- process.exit(0);
1238
- }
1515
+ const isMain =
1516
+ process.argv[1] &&
1517
+ (process.argv[1].endsWith("scaffold.js") ||
1518
+ process.argv[1].endsWith("ebade"));
1519
+
1520
+ if (isMain) {
1521
+ if (
1522
+ args.length === 0 ||
1523
+ command === "help" ||
1524
+ command === "--help" ||
1525
+ command === "-h"
1526
+ ) {
1527
+ showHelp();
1528
+ process.exit(0);
1529
+ }
1239
1530
 
1240
- if (command === "init") {
1241
- await init();
1242
- } else if (command === "scaffold") {
1243
- const ebadeFile = args[1];
1244
- const outputDir = args[2] || "./output";
1531
+ if (command === "init") {
1532
+ await init();
1533
+ } else if (command === "scaffold") {
1534
+ const ebadeFile = args[1];
1535
+ const outputDir = args[2] || "./output";
1245
1536
 
1246
- if (!ebadeFile) {
1247
- console.error(
1248
- `${colors.red}Error:${colors.reset} Please provide an ebade file path.`
1249
- );
1250
- console.log(
1251
- `\n${colors.dim}Usage:${colors.reset} npx ebade scaffold <file.ebade.yaml> [output-dir]\n`
1252
- );
1253
- process.exit(1);
1254
- }
1537
+ if (!ebadeFile) {
1538
+ console.error(
1539
+ `${colors.red}Error:${colors.reset} Please provide an ebade file path.`
1540
+ );
1541
+ console.log(
1542
+ `\n${colors.dim}Usage:${colors.reset} npx ebade scaffold <file.ebade.yaml> [output-dir]\n`
1543
+ );
1544
+ process.exit(1);
1545
+ }
1255
1546
 
1256
- if (!fs.existsSync(ebadeFile)) {
1257
- console.error(
1258
- `${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
1259
- );
1260
- process.exit(1);
1261
- }
1547
+ if (!fs.existsSync(ebadeFile)) {
1548
+ console.error(
1549
+ `${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
1550
+ );
1551
+ process.exit(1);
1552
+ }
1262
1553
 
1263
- await scaffold(ebadeFile, outputDir);
1264
- } else if (command === "playground") {
1265
- console.log(`\n${colors.cyan}๐ŸŒ Opening ebade playground...${colors.reset}`);
1266
- const url = "https://ebade.dev/playground";
1267
- const start =
1268
- process.platform === "darwin"
1269
- ? "open"
1270
- : process.platform === "win32"
1271
- ? "start"
1272
- : "xdg-open";
1273
- try {
1274
- execSync(`${start} ${url}`);
1275
- } catch (e) {
1276
- console.log(`\n${colors.yellow}Please open:${colors.reset} ${url}`);
1277
- }
1278
- } else if (command === "dev") {
1279
- const ebadeFile = args[1];
1280
- const outputDir = args[2] || "./output";
1554
+ await scaffold(ebadeFile, outputDir);
1555
+ } else if (command === "build") {
1556
+ const prompt = args.slice(1).join(" ");
1281
1557
 
1282
- if (!ebadeFile) {
1283
- console.error(
1284
- `${colors.red}Error:${colors.reset} Please provide an ebade file path.`
1558
+ if (!prompt) {
1559
+ console.error(
1560
+ `${colors.red}Error:${colors.reset} Please provide a prompt. e.g. npx ebade build "A blue themed SaaS"`
1561
+ );
1562
+ process.exit(1);
1563
+ }
1564
+
1565
+ const spinner = ora(
1566
+ `${colors.cyan}EbadeArchitect is designing your project...${colors.reset}`
1567
+ ).start();
1568
+ const config = await EbadeArchitect.plan(prompt);
1569
+ spinner.succeed(
1570
+ `Design complete: ${colors.bright}${config.name}${colors.reset}`
1285
1571
  );
1572
+
1573
+ const outputDir = "./" + (config.name || "ebade-app");
1574
+
1575
+ // Create temporary YAML and scaffold
1576
+ const tempDir = path.join(process.cwd(), ".ebade_temp");
1577
+ ensureDir(tempDir);
1578
+ const tempFile = path.join(tempDir, "project.ebade.yaml");
1579
+ fs.writeFileSync(tempFile, yaml.stringify(config));
1580
+
1581
+ await scaffold(tempFile, outputDir);
1582
+
1583
+ // Final Brief
1584
+ console.log(`
1585
+ ${colors.magenta}${colors.bright}๐ŸŽจ Creative Brief from EbadeArchitect:${
1586
+ colors.reset
1587
+ }
1588
+ ${colors.cyan}Project:${colors.reset} ${config.name}
1589
+ ${colors.cyan}Theme:${colors.reset} ${
1590
+ config.design.colors.primary
1591
+ } (Detected from prompt)
1592
+ ${colors.cyan}Pages:${colors.reset} ${config.pages
1593
+ .map((p) => p.path)
1594
+ .join(", ")}
1595
+ ${colors.cyan}Features:${colors.reset} ${config.features.join(", ")}
1596
+ `);
1597
+
1598
+ // Cleanup
1599
+ fs.rmSync(tempDir, { recursive: true, force: true });
1600
+ } else if (command === "playground") {
1286
1601
  console.log(
1287
- `\n${colors.dim}Usage:${colors.reset} npx ebade dev <file.ebade.yaml> [output-dir]\n`
1602
+ `\n${colors.cyan}๐ŸŒ Opening ebade playground...${colors.reset}`
1288
1603
  );
1289
- process.exit(1);
1290
- }
1604
+ const url = "https://ebade.dev/playground";
1605
+ const start =
1606
+ process.platform === "darwin"
1607
+ ? "open"
1608
+ : process.platform === "win32"
1609
+ ? "start"
1610
+ : "xdg-open";
1611
+ try {
1612
+ execSync(`${start} ${url}`);
1613
+ } catch (e) {
1614
+ console.log(`\n${colors.yellow}Please open:${colors.reset} ${url}`);
1615
+ }
1616
+ } else if (command === "dev") {
1617
+ const ebadeFile = args[1];
1618
+ const outputDir = args[2] || "./output";
1619
+
1620
+ if (!ebadeFile) {
1621
+ console.error(
1622
+ `${colors.red}Error:${colors.reset} Please provide an ebade file path.`
1623
+ );
1624
+ console.log(
1625
+ `\n${colors.dim}Usage:${colors.reset} npx ebade dev <file.ebade.yaml> [output-dir]\n`
1626
+ );
1627
+ process.exit(1);
1628
+ }
1291
1629
 
1292
- if (!fs.existsSync(ebadeFile)) {
1630
+ if (!fs.existsSync(ebadeFile)) {
1631
+ console.error(
1632
+ `${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
1633
+ );
1634
+ process.exit(1);
1635
+ }
1636
+
1637
+ await dev(ebadeFile, outputDir);
1638
+ } else {
1293
1639
  console.error(
1294
- `${colors.red}Error:${colors.reset} ebade file not found: ${ebadeFile}`
1640
+ `${colors.red}Error:${colors.reset} Unknown command: ${command}`
1295
1641
  );
1642
+ showHelp();
1296
1643
  process.exit(1);
1297
1644
  }
1298
-
1299
- await dev(ebadeFile, outputDir);
1300
- } else {
1301
- console.error(
1302
- `${colors.red}Error:${colors.reset} Unknown command: ${command}`
1303
- );
1304
- showHelp();
1305
- process.exit(1);
1306
1645
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ebade",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "description": "ebade - Agent-First Framework. The first framework designed for AI agents, readable by humans.",
5
5
  "type": "module",
6
6
  "main": "cli/scaffold.js",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ebade-mcp-server",
3
- "version": "0.4.3",
4
- "description": "MCP Server for ebade v0.4.3 - The Agent-First Framework",
3
+ "version": "0.4.6",
4
+ "description": "MCP Server for ebade v0.4.6 - The Agent-First Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -9,6 +9,7 @@
9
9
  * - Validate ebade files
10
10
  * - Compile ebade to framework-specific code
11
11
  * - Generate components from natural language descriptions
12
+ * - Build entire projects from natural language prompts (NEW in v0.4.6)
12
13
  */
13
14
 
14
15
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -24,12 +25,13 @@ import { scaffoldProject } from "./tools/scaffold.js";
24
25
  import { validateIntent } from "./tools/validate.js";
25
26
  import { compileIntent } from "./tools/compile.js";
26
27
  import { generateComponent } from "./tools/generate.js";
28
+ import { buildFromPrompt } from "./tools/build.js";
27
29
 
28
30
  // Create the MCP server
29
31
  const server = new Server(
30
32
  {
31
33
  name: "ebade",
32
- version: "0.3.1",
34
+ version: "0.4.6",
33
35
  },
34
36
  {
35
37
  capabilities: {
@@ -183,6 +185,33 @@ Use this when:
183
185
  required: ["description"],
184
186
  },
185
187
  },
188
+ {
189
+ name: "ebade_build",
190
+ description: `Revolutionary "Prompt-to-Product" tool.
191
+ Generates a complete, production-ready project from a single natural language description.
192
+
193
+ Use this when the user says:
194
+ - "Bana mor temalฤฑ bir kripto borsasฤฑ yap"
195
+ - "Create a red themed SaaS for AI model store"
196
+ - "Make a sleek portfolio for a creative director"
197
+
198
+ This tool handles architecture, component selection, color palette, and scaffolding in one shot.`,
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ prompt: {
203
+ type: "string",
204
+ description: "The natural language instruction for the project",
205
+ },
206
+ outputDir: {
207
+ type: "string",
208
+ description:
209
+ "Base directory path where the project will be created (absolute)",
210
+ },
211
+ },
212
+ required: ["prompt", "outputDir"],
213
+ },
214
+ },
186
215
  ],
187
216
  };
188
217
  });
@@ -229,6 +258,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
229
258
  style: args?.style as any,
230
259
  });
231
260
 
261
+ case "ebade_build":
262
+ return {
263
+ content: [
264
+ {
265
+ type: "text",
266
+ text: await buildFromPrompt(args as any),
267
+ },
268
+ ],
269
+ };
270
+
232
271
  default:
233
272
  throw new Error(`Unknown tool: ${name}`);
234
273
  }
@@ -278,7 +317,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
278
317
  const { uri } = request.params;
279
318
 
280
319
  const resources: Record<string, string> = {
281
- "ebade://syntax": `# ebade Syntax Reference\\n\\n@page, @ebade, @requires, @outcomes, @data, @validate, @style, @compose, @on, @expects\\n\\nCode = f(ebade)`,
320
+ "ebade://syntax": `# ebade Syntax Reference\n\n@page, @ebade, @requires, @outcomes, @data, @validate, @style, @compose, @on, @expects\n\nCode = f(ebade)`,
282
321
  "ebade://examples/ecommerce": `# E-commerce ebade\nname: my-store\ntype: e-commerce\nfeatures:\n - product-catalog\n - shopping-cart\n - checkout`,
283
322
  "ebade://examples/saas": `# SaaS ebade\nname: my-saas\ntype: saas-dashboard\nfeatures:\n - user-auth\n - billing\n - analytics`,
284
323
  };
@@ -0,0 +1,57 @@
1
+ import { execSync } from "child_process";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ interface BuildArgs {
6
+ prompt: string;
7
+ outputDir: string;
8
+ }
9
+
10
+ export async function buildFromPrompt(args: BuildArgs): Promise<string> {
11
+ const { prompt, outputDir } = args;
12
+
13
+ try {
14
+ // Find the CLI script path
15
+ // Assuming we are in packages/mcp-server/dist/tools/ (at runtime)
16
+ // or packages/mcp-server/src/tools/ (at dev time)
17
+ const cliPath = path.resolve(process.cwd(), "../../cli/scaffold.js");
18
+
19
+ if (!fs.existsSync(cliPath)) {
20
+ throw new Error(`CLI not found at ${cliPath}`);
21
+ }
22
+
23
+ console.log(`๐Ÿš€ Executing ebade build from prompt: "${prompt}"`);
24
+
25
+ // Execute the CLI command
26
+ // We pass the prompt as an argument.
27
+ // We need to handle the outputDir carefully.
28
+ // The CLI build command currently uses its own naming logic,
29
+ // but it creates the folder in the current process.cwd().
30
+
31
+ const cmd = `node "${cliPath}" build "${prompt}"`;
32
+
33
+ // Run in the specified outputDir if provided
34
+ const result = execSync(cmd, {
35
+ cwd: outputDir,
36
+ encoding: "utf-8",
37
+ env: { ...process.env, NODE_ENV: "production" },
38
+ });
39
+
40
+ return `โœ… ebade v0.4.6 Build Complete!
41
+
42
+ Prompt: "${prompt}"
43
+ Output: ${outputDir}
44
+
45
+ CLI Output:
46
+ ${result}
47
+
48
+ Next Steps:
49
+ 1. Navigate to the generated folder.
50
+ 2. Run 'npm install && npm run dev'.
51
+ 3. Enjoy your turnkey project!`;
52
+ } catch (error) {
53
+ throw new Error(
54
+ `Build failed: ${error instanceof Error ? error.message : String(error)}`
55
+ );
56
+ }
57
+ }
@@ -0,0 +1,66 @@
1
+ import { execSync } from "child_process";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import assert from "assert";
5
+
6
+ async function runCliTests() {
7
+ console.log("๐Ÿงช Running CLI Integration Tests...\n");
8
+
9
+ const PROJECT_NAME = "ModernBlog";
10
+ if (fs.existsSync(PROJECT_NAME)) {
11
+ fs.rmSync(PROJECT_NAME, { recursive: true, force: true });
12
+ }
13
+
14
+ try {
15
+ // 1. Test 'ebade build' command
16
+ console.log(`Testing 'ebade build' command for ${PROJECT_NAME}...`);
17
+ const cmd = `node cli/scaffold.js build "A Modern Blog with blue theme"`;
18
+
19
+ execSync(cmd, { stdio: "inherit" });
20
+
21
+ const projectDir = path.join(process.cwd(), PROJECT_NAME);
22
+
23
+ assert.ok(
24
+ fs.existsSync(projectDir),
25
+ `Project directory (${PROJECT_NAME}) should exist`
26
+ );
27
+ assert.ok(
28
+ fs.existsSync(path.join(projectDir, "project.ebade.yaml")),
29
+ "project.ebade.yaml should be generated"
30
+ );
31
+ assert.ok(
32
+ fs.existsSync(path.join(projectDir, "app/page.tsx")),
33
+ "Main page should be generated"
34
+ );
35
+ assert.ok(
36
+ fs.existsSync(path.join(projectDir, ".env.example")),
37
+ ".env.example should be generated"
38
+ );
39
+ assert.ok(
40
+ fs.existsSync(path.join(projectDir, ".gitignore")),
41
+ ".gitignore should be generated"
42
+ );
43
+
44
+ // Verify blog pages
45
+ assert.ok(
46
+ fs.existsSync(path.join(projectDir, "app/posts/page.tsx")),
47
+ "Blog index page should be generated"
48
+ );
49
+
50
+ console.log("โœ… 'ebade build' Integration OK");
51
+ } catch (err) {
52
+ console.error("\nโŒ CLI Test failed:");
53
+ console.error(err);
54
+ process.exit(1);
55
+ } finally {
56
+ // Cleanup
57
+ console.log("\n๐Ÿงน Cleaning up test artifacts...");
58
+ if (fs.existsSync(PROJECT_NAME)) {
59
+ fs.rmSync(PROJECT_NAME, { recursive: true, force: true });
60
+ }
61
+ }
62
+
63
+ console.log("\nโœจ CLI integration tests passed!");
64
+ }
65
+
66
+ runCliTests();
@@ -0,0 +1,102 @@
1
+ import {
2
+ EbadeArchitect,
3
+ toPascalCase,
4
+ toSnakeCase,
5
+ hexToHsl,
6
+ } from "../../cli/scaffold.js";
7
+ import assert from "assert";
8
+
9
+ async function runTests() {
10
+ console.log("๐Ÿงช Running Framework Unit Tests (v0.4.6)...\n");
11
+
12
+ // 1. Utility Functions
13
+ console.log("Testing Utility Functions...");
14
+ assert.strictEqual(toPascalCase("hello-world"), "HelloWorld");
15
+ assert.strictEqual(toPascalCase("hello_world"), "HelloWorld");
16
+ assert.strictEqual(toSnakeCase("HelloWorld"), "hello_world");
17
+ assert.strictEqual(toSnakeCase("hello-world"), "hello_world");
18
+
19
+ // hexToHsl Test (Critical for Design System)
20
+ const hsl = hexToHsl("#8b5cf6"); // Violet
21
+ assert.ok(
22
+ hsl.includes("258"),
23
+ `HSL for #8b5cf6 should include hue 258, got ${hsl}`
24
+ );
25
+ console.log("โœ… Utilities OK");
26
+
27
+ // 2. Architect Mapping
28
+ console.log("\nTesting EbadeArchitect (Complex Intent)...");
29
+ const prompt =
30
+ "Can you make a luxury gold themed dashboard with charts and login features?";
31
+ const config = await EbadeArchitect.plan(prompt);
32
+
33
+ // Check intelligence
34
+ assert.strictEqual(
35
+ config.name,
36
+ "LuxuryGold",
37
+ "Should filter filler words like 'Can you make a'"
38
+ );
39
+ assert.strictEqual(
40
+ config.design.colors.primary,
41
+ "#fbbf24",
42
+ "Gold detection failed"
43
+ );
44
+ assert.ok(
45
+ config.features.includes("Advanced Analytics"),
46
+ "Analytics detection failed"
47
+ );
48
+ assert.ok(
49
+ config.features.includes("Authentication"),
50
+ "Auth detection failed"
51
+ );
52
+
53
+ // Check Type-Aware Pages
54
+ assert.strictEqual(
55
+ config.pages.length,
56
+ 2,
57
+ "Should have landing and dashboard"
58
+ );
59
+ assert.ok(
60
+ config.pages.some((p) => p.path === "/dashboard"),
61
+ "Dashboard page missing"
62
+ );
63
+ console.log("โœ… Complex Intent mapping OK");
64
+
65
+ // 3. Project Type Specifics (E-commerce)
66
+ console.log("\nTesting EbadeArchitect (E-commerce)...");
67
+ const shopConfig = await EbadeArchitect.plan(
68
+ "Sleek shoe store with red theme"
69
+ );
70
+ assert.strictEqual(shopConfig.type, "e-commerce");
71
+ assert.ok(
72
+ shopConfig.pages.some((p) => p.path === "/products"),
73
+ "E-commerce missing /products"
74
+ );
75
+ assert.ok(
76
+ shopConfig.pages.some((p) => p.path === "/cart"),
77
+ "E-commerce missing /cart"
78
+ );
79
+ console.log("โœ… E-commerce structure OK");
80
+
81
+ // 4. Project Type Specifics (Blog)
82
+ console.log("\nTesting EbadeArchitect (Blog)...");
83
+ const blogConfig = await EbadeArchitect.plan("Ocean themed news blog");
84
+ assert.strictEqual(blogConfig.type, "blog");
85
+ assert.ok(
86
+ blogConfig.pages.some((p) => p.path === "/posts"),
87
+ "Blog missing /posts index"
88
+ );
89
+ assert.ok(
90
+ blogConfig.pages.some((p) => p.path === "/posts/[slug]"),
91
+ "Blog missing /posts/[slug] dynamic route"
92
+ );
93
+ console.log("โœ… Blog structure OK");
94
+
95
+ console.log("\nโœจ All framework tests passed!");
96
+ }
97
+
98
+ runTests().catch((err) => {
99
+ console.error("\nโŒ Test failed:");
100
+ console.error(err);
101
+ process.exit(1);
102
+ });
package/www/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "www",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",