@africode/core 5.0.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 (136) hide show
  1. package/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
  2. package/LICENSE +623 -0
  3. package/README.md +442 -0
  4. package/bin/africode.js +73 -0
  5. package/bin/africode.js.1758507140 +343 -0
  6. package/bin/cli.ts +83 -0
  7. package/bin/create-africode.js +158 -0
  8. package/bin/scaffold.ts +219 -0
  9. package/components/accordion.js +183 -0
  10. package/components/alert.js +131 -0
  11. package/components/auth.js +172 -0
  12. package/components/avatar.js +117 -0
  13. package/components/badge.js +104 -0
  14. package/components/base.d.ts +139 -0
  15. package/components/base.js +184 -0
  16. package/components/button.js +164 -0
  17. package/components/card.js +137 -0
  18. package/components/cultural-card.js +243 -0
  19. package/components/divider.js +83 -0
  20. package/components/dropdown.js +171 -0
  21. package/components/error-boundary.js +155 -0
  22. package/components/form.js +131 -0
  23. package/components/grid.js +273 -0
  24. package/components/hero.js +138 -0
  25. package/components/icon.js +36 -0
  26. package/components/index.js +57 -0
  27. package/components/input.js +256 -0
  28. package/components/kanga-card.js +185 -0
  29. package/components/language-switcher.js +108 -0
  30. package/components/loader.js +80 -0
  31. package/components/modal.js +262 -0
  32. package/components/motion.js +84 -0
  33. package/components/navbar.js +236 -0
  34. package/components/pattern-showcase.js +225 -0
  35. package/components/progress.js +134 -0
  36. package/components/react.js +111 -0
  37. package/components/section.js +54 -0
  38. package/components/select.js +322 -0
  39. package/components/sidebar.js +180 -0
  40. package/components/skeleton.js +85 -0
  41. package/components/table.js +181 -0
  42. package/components/tabs.js +202 -0
  43. package/components/theme-toggle.js +82 -0
  44. package/components/toast.js +139 -0
  45. package/components/tooltip.js +167 -0
  46. package/core/a2ui-schema-manager.js +344 -0
  47. package/core/a2ui.js +431 -0
  48. package/core/bun-runtime.js +799 -0
  49. package/core/cli/commands/add.js +23 -0
  50. package/core/cli/commands/audit.js +58 -0
  51. package/core/cli/commands/build.js +137 -0
  52. package/core/cli/commands/create-plugin.js +241 -0
  53. package/core/cli/commands/dev.js +228 -0
  54. package/core/cli/commands/lint.js +23 -0
  55. package/core/cli/commands/test.js +34 -0
  56. package/core/cli/migrator.js +71 -0
  57. package/core/cli/ui.js +46 -0
  58. package/core/compliance.js +628 -0
  59. package/core/config.js +263 -0
  60. package/core/db-advanced.js +481 -0
  61. package/core/db.js +284 -0
  62. package/core/enhanced-hmr.js +404 -0
  63. package/core/errors.js +222 -0
  64. package/core/file-router.js +290 -0
  65. package/core/heartbeat.js +64 -0
  66. package/core/hmr-client.js +204 -0
  67. package/core/hmr.js +196 -0
  68. package/core/html.d.ts +116 -0
  69. package/core/html.js +160 -0
  70. package/core/hydration.js +52 -0
  71. package/core/lipa-namba-journey.js +572 -0
  72. package/core/motion.js +106 -0
  73. package/core/nida-cig-middleware.js +455 -0
  74. package/core/patterns.d.ts +124 -0
  75. package/core/patterns.js +833 -0
  76. package/core/plugins/index.js +312 -0
  77. package/core/router.js +387 -0
  78. package/core/sdk-client.js +62 -0
  79. package/core/sdk.d.ts +133 -0
  80. package/core/sdk.js +123 -0
  81. package/core/seo.js +76 -0
  82. package/core/server/auth-endpoints.js +339 -0
  83. package/core/server/auth.js +180 -0
  84. package/core/server/csrf.js +206 -0
  85. package/core/server/db.js +39 -0
  86. package/core/server/middleware.js +324 -0
  87. package/core/server/rate-limit.js +238 -0
  88. package/core/server/render.js +69 -0
  89. package/core/server/router.js +120 -0
  90. package/core/shim.js +28 -0
  91. package/core/state.d.ts +86 -0
  92. package/core/state.js +242 -0
  93. package/core/store.d.ts +122 -0
  94. package/core/store.js +61 -0
  95. package/core/validation.d.ts +233 -0
  96. package/core/validation.js +590 -0
  97. package/core/websocket.js +639 -0
  98. package/dist/africode.js +2905 -0
  99. package/dist/africode.js.map +61 -0
  100. package/dist/build-info.json +23 -0
  101. package/dist/components.js +2888 -0
  102. package/dist/components.js.map +58 -0
  103. package/dist/styles/africanity.css +322 -0
  104. package/dist/styles/typography.css +141 -0
  105. package/docs/IDE-Guide.md +50 -0
  106. package/package.json +110 -0
  107. package/src/index.ts +196 -0
  108. package/styles/africanity.css +322 -0
  109. package/styles/typography.css +141 -0
  110. package/templates/starter/.env.example +15 -0
  111. package/templates/starter/africode.config.js +40 -0
  112. package/templates/starter/package.json +14 -0
  113. package/templates/starter/src/pages/index.html +46 -0
  114. package/templates/starter/src/pages/index.js +32 -0
  115. package/templates/starter/src/styles/main.css +4 -0
  116. package/templates/starter-3d/.env.example +7 -0
  117. package/templates/starter-3d/africode.config.js +29 -0
  118. package/templates/starter-3d/components/af-model-viewer.js +125 -0
  119. package/templates/starter-3d/package.json +15 -0
  120. package/templates/starter-3d/src/pages/index.html +46 -0
  121. package/templates/starter-3d/src/pages/index.js +50 -0
  122. package/templates/starter-3d/src/styles/main.css +4 -0
  123. package/templates/starter-react/.env.example +15 -0
  124. package/templates/starter-react/africode.config.js +40 -0
  125. package/templates/starter-react/package.json +16 -0
  126. package/templates/starter-react/src/pages/index.html +46 -0
  127. package/templates/starter-react/src/pages/index.js +68 -0
  128. package/templates/starter-react/src/styles/main.css +4 -0
  129. package/templates/starter-tailwind/.env.example +15 -0
  130. package/templates/starter-tailwind/africode.config.js +40 -0
  131. package/templates/starter-tailwind/package.json +20 -0
  132. package/templates/starter-tailwind/src/pages/index.html +46 -0
  133. package/templates/starter-tailwind/src/pages/index.js +37 -0
  134. package/templates/starter-tailwind/src/styles/main.css +4 -0
  135. package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
  136. package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * AfriCode CLI
4
+ *
5
+ * Commands:
6
+ * africode dev - Start development server
7
+ * africode build - Production build
8
+ * africode add - Add component to project
9
+ *
10
+ * @module bin/africode
11
+ */
12
+
13
+ // --- SSR DOM SHIM (Must be first) ---
14
+ if (typeof globalThis.HTMLElement === 'undefined') {
15
+ globalThis.HTMLElement = class HTMLElement { };
16
+ globalThis.customElements = { define: () => { }, get: () => { } };
17
+ globalThis.document = {
18
+ createElement: () => ({
19
+ classList: { add: () => { } },
20
+ setAttribute: () => { },
21
+ style: {}
22
+ }),
23
+ head: { appendChild: () => { } },
24
+ body: { appendChild: () => { } }
25
+ };
26
+ globalThis.window = globalThis;
27
+ globalThis.CSSStyleSheet = class CSSStyleSheet { replaceSync() { } };
28
+ }
29
+ // ------------------------------------
30
+
31
+ const args = process.argv.slice(2);
32
+ const command = args[0];
33
+ // Allows Web Components to be imported in Node/Bun environment
34
+ if (typeof global !== 'undefined') {
35
+ global.HTMLElement = class HTMLElement { };
36
+ global.customElements = {
37
+ define: () => { },
38
+ get: () => { }
39
+ };
40
+ global.document = {
41
+ createElement: () => ({ classList: { add: () => { } }, setAttribute: () => { } }),
42
+ head: { appendChild: () => { } }
43
+ };
44
+ global.window = global;
45
+ }
46
+ // --------------------
47
+
48
+ const colors = {
49
+ green: '\x1b[32m',
50
+ gold: '\x1b[33m',
51
+ blue: '\x1b[34m',
52
+ red: '\x1b[31m',
53
+ reset: '\x1b[0m',
54
+ bold: '\x1b[1m'
55
+ };
56
+
57
+ function logo() {
58
+ console.log(`
59
+ ${colors.gold}╔═══════════════════════════════════════╗
60
+ ║ ║
61
+ ║ ${colors.green}█████╗ ███████╗██████╗ ██╗${colors.gold} ║
62
+ ║ ${colors.green}██╔══██╗██╔════╝██╔══██╗██║${colors.gold} ║
63
+ ║ ${colors.green}███████║█████╗ ██████╔╝██║${colors.gold} ║
64
+ ║ ${colors.green}██╔══██║██╔══╝ ██╔══██╗██║${colors.gold} ║
65
+ ║ ${colors.green}██║ ██║██║ ██║ ██║██║${colors.gold} ║
66
+ ║ ${colors.green}╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝${colors.gold} ║
67
+ ║ ║
68
+ ║ ${colors.reset}The Rhythmic Web Framework${colors.gold} ║
69
+ ╚═══════════════════════════════════════╝${colors.reset}
70
+ `);
71
+ }
72
+
73
+ function help() {
74
+ logo();
75
+ console.log(`
76
+ ${colors.bold}Usage:${colors.reset}
77
+ africode <command> [options]
78
+
79
+ ${colors.bold}Commands:${colors.reset}
80
+ ${colors.green}dev${colors.reset} Start development server
81
+ ${colors.green}build${colors.reset} Create production build
82
+ ${colors.green}add${colors.reset} Add a component (e.g., africode add sidebar)
83
+ ${colors.green}new${colors.reset} Create a new project (options: --template 3d)
84
+ ${colors.green}migrate${colors.reset} Run database migrations
85
+ ${colors.green}help${colors.reset} Show this help message
86
+
87
+ ${colors.bold}Examples:${colors.reset}
88
+ africode dev
89
+ africode new my-app --template 3d
90
+ africode add toast
91
+
92
+ ${colors.gold}Powered by the rhythm of the continent.${colors.reset}
93
+ `);
94
+ }
95
+
96
+ async function dev() {
97
+ logo();
98
+ console.log(`${colors.green}▶ Starting development server...${colors.reset}\n`);
99
+
100
+ const server = Bun.serve({
101
+ port: 3000,
102
+ async fetch(req) {
103
+ const url = new URL(req.url);
104
+ let path = url.pathname;
105
+
106
+ // 1. API Route Delegation
107
+ if (path.startsWith('/api/')) {
108
+ const { handleApiRequest } = await import('../core/server/router.js');
109
+ const apiRes = await handleApiRequest(req);
110
+ if (apiRes) return apiRes;
111
+ }
112
+
113
+ // 2. Root Redirect
114
+ if (path === '/') path = '/index';
115
+
116
+ // 3. JS-First Page Rendering (Next.js Style)
117
+ // Route /about -> looks for pages/about.js
118
+ const pageName = path.replace(/^\//, '').replace(/\.html$/, '');
119
+ const jsPagePath = `./pages/${pageName}.js`;
120
+
121
+ if (await Bun.file(jsPagePath).exists()) {
122
+ try {
123
+ // Dynamic Import with Cache Busting (for Hot Reloading in Dev)
124
+ const pageModule = await import(`${process.cwd()}/pages/${pageName}.js?t=${Date.now()}`);
125
+
126
+ // execute loader if present
127
+ let loaderData = {};
128
+ if (typeof pageModule.loader === 'function') {
129
+ try {
130
+ loaderData = await pageModule.loader({ request: req, params: {} });
131
+ } catch (loadErr) {
132
+ console.error(`Loader failed for ${pageName}:`, loadErr);
133
+ return new Response(`Loader Error: ${loadErr.message}`, { status: 500 });
134
+ }
135
+ }
136
+
137
+ // Render Default Export
138
+ if (typeof pageModule.default === 'function') {
139
+ // Pass loader data to the component
140
+ const htmlContent = pageModule.default({ data: loaderData });
141
+ return new Response(htmlContent, {
142
+ headers: { 'Content-Type': 'text/html' }
143
+ });
144
+ }
145
+ } catch (err) {
146
+ console.error(`Error rendering page ${pageName}:`, err);
147
+ return new Response(`Error rendering page: ${err.message}`, { status: 500 });
148
+ }
149
+ }
150
+
151
+ // 4. Fallback to Static HTML (Legacy Support)
152
+ // If .js doesn't exist, looking for .html
153
+ if (await Bun.file(`./pages${path}.html`).exists()) {
154
+ const { renderPage } = await import('../core/server/render.js');
155
+ return await renderPage(`./pages${path}.html`);
156
+ }
157
+
158
+ // 5. Static Assets (CSS, Images, etc)
159
+ const file = Bun.file('.' + url.pathname); // Use original path for assets
160
+
161
+ if (await file.exists()) {
162
+
163
+ // SSR Hook for HTML
164
+ if (path.endsWith('.html')) {
165
+ const { renderPage } = await import('../core/server/render.js');
166
+ return await renderPage('.' + path);
167
+ }
168
+
169
+ const ext = path.split('.').pop();
170
+ const types = {
171
+ html: 'text/html', css: 'text/css', js: 'application/javascript',
172
+ json: 'application/json', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg'
173
+ };
174
+ return new Response(file, {
175
+ headers: { 'Content-Type': types[ext] || 'application/octet-stream' }
176
+ });
177
+ }
178
+
179
+ return new Response('404 Not Found', { status: 404 });
180
+ }
181
+ });
182
+
183
+ console.log(`${colors.green}✓ Server running at ${colors.blue}http://localhost:${server.port}${colors.reset}`);
184
+ console.log(`${colors.gold} Press Ctrl+C to stop${colors.reset}\n`);
185
+ }
186
+
187
+ async function build() {
188
+ logo();
189
+ console.log(`${colors.green}▶ Building for production (SSG + Islands)...${colors.reset}\n`);
190
+
191
+ const { rm, cp, mkdir, readdir, writeFile } = await import('fs/promises');
192
+ const path = await import('path');
193
+ const distDir = './dist';
194
+ const pagesDir = './pages';
195
+
196
+ try {
197
+ // Clean
198
+ await rm(distDir, { recursive: true, force: true });
199
+ await mkdir(distDir);
200
+
201
+ // 1. Bundle Core SDK & Components (Islands)
202
+ console.log(' Bundling JavaScript Islands...');
203
+ const buildResult = await Bun.build({
204
+ entrypoints: ['./core/sdk.js', './components/index.js'],
205
+ outdir: './dist/assets',
206
+ minify: true,
207
+ splitting: true,
208
+ target: 'browser',
209
+ naming: '[name]-[hash].[ext]' // Cache busting
210
+ });
211
+
212
+ if (!buildResult.success) {
213
+ throw new Error(buildResult.logs.join('\n'));
214
+ }
215
+
216
+ // 2. Static Site Generation (Pre-rendering)
217
+ console.log(' Pre-rendering Pages (SSG)...');
218
+ const pageFiles = await readdir(pagesDir);
219
+ const routes = [];
220
+
221
+ // Need the renderPage logic but adapted for Module imports
222
+ // We can't easily reuse the dev server logic verbatim, so we implement the SSG loop here.
223
+
224
+ for (const file of pageFiles) {
225
+ if (!file.endsWith('.js')) continue;
226
+
227
+ const name = file.replace('.js', '');
228
+ const route = name === 'index' ? '/' : `/${name}`;
229
+ routes.push(route);
230
+
231
+ console.log(` • Rendering ${route}...`);
232
+
233
+ // Dynamic Import of the Page Component
234
+ // CAUTION: In a real CLI, we might need to transpile or ignore CSS imports if they exist in JS.
235
+ // But since we use vanilla JS + tagged templates, it should work in Bun.
236
+ const pagePath = path.resolve(process.cwd(), pagesDir, file);
237
+ const pageModule = await import(pagePath);
238
+
239
+ // Execute Loader (Server-Side Data Fetching)
240
+ let loaderData = {};
241
+ if (typeof pageModule.loader === 'function') {
242
+ loaderData = await pageModule.loader({ request: new Request(`http://localhost${route}`), params: {} });
243
+ }
244
+
245
+ // Render HTML
246
+ if (typeof pageModule.default === 'function') {
247
+ const htmlContent = pageModule.default({ data: loaderData });
248
+
249
+ // We need to inject the hashed SDK script paths into the HTML
250
+ // For simplicity, we'll assume the standard names or use a manifest interaction if we were more advanced.
251
+ // For now, let's keep the SDK imports standard in Layout (or replace them here).
252
+ // NOTE: The Layout component currently hardcodes /core/sdk.js.
253
+ // To support hashing, we'd need to update Layout or use a transform here.
254
+ // We will stick to standard paths for the "Islands" bundles for now to keep it simple.
255
+ // Or better: write a small logic to replace the script src.
256
+
257
+ const outputPath = path.join(distDir, `${name}.html`);
258
+ await writeFile(outputPath, htmlContent);
259
+ }
260
+ }
261
+
262
+ // 3. Generate Sitemap
263
+ console.log(' Generating Sitemap...');
264
+ const { generateSitemap } = await import('../core/seo.js');
265
+ const sitemapXml = generateSitemap('https://your-domain.com', routes);
266
+ await writeFile(path.join(distDir, 'sitemap.xml'), sitemapXml);
267
+
268
+ // 4. Copy Static Assets
269
+ console.log(' Copying Assets...');
270
+ if (await Bun.file('./styles').exists()) await cp('./styles', `${distDir}/styles`, { recursive: true });
271
+ // Copy public assets if any (assuming root level assets or create a public folder later)
272
+
273
+ // 5. Create Server Entry (Optional, for dynamic hosting)
274
+ const serverBuild = await Bun.build({
275
+ entrypoints: ['./server.js'],
276
+ outdir: './dist',
277
+ target: 'bun'
278
+ });
279
+
280
+ console.log(`${colors.green}✓ Build complete!${colors.reset}`);
281
+ console.log(` Output: ${colors.blue}./dist${colors.reset} (Ready to deploy)`);
282
+ console.log(` Pages: ${colors.bold}${routes.length}${colors.reset} pre-rendered`);
283
+ console.log(` Sitemap: ${colors.blue}./dist/sitemap.xml${colors.reset}\n`);
284
+
285
+ } catch (err) {
286
+ console.error(`${colors.red}✗ Build failed:${colors.reset}`, err.message || err);
287
+ }
288
+ }
289
+
290
+ async function addComponent(name) {
291
+ if (!name) {
292
+ console.log(`${colors.red}✗ Please specify a component name${colors.reset}`);
293
+ console.log(` Example: africode add sidebar\n`);
294
+ return;
295
+ }
296
+
297
+ const components = {
298
+ sidebar: 'af-sidebar',
299
+ tooltip: 'af-tooltip',
300
+ toast: 'af-toast',
301
+ skeleton: 'af-skeleton',
302
+ dropdown: 'af-dropdown'
303
+ };
304
+
305
+ if (!components[name]) {
306
+ console.log(`${colors.red}✗ Unknown component: ${name}${colors.reset}`);
307
+ console.log(` Available: ${Object.keys(components).join(', ')}\n`);
308
+ return;
309
+ }
310
+
311
+ console.log(`${colors.green}✓ Component ${colors.gold}${components[name]}${colors.green} is available in AfriCode!${colors.reset}`);
312
+ console.log(` Import with: import './components/${name}.js';\n`);
313
+ }
314
+
315
+ // Command router
316
+ switch (command) {
317
+ case 'dev':
318
+ dev();
319
+ break;
320
+ case 'build':
321
+ build();
322
+ break;
323
+ case 'add':
324
+ addComponent(args[1]);
325
+ break;
326
+ case 'migrate':
327
+ const { runMigrations } = await import('../core/cli/migrator.js');
328
+ await runMigrations();
329
+ break;
330
+ case 'new':
331
+ case 'create':
332
+ case 'init':
333
+ const { createProject } = await import('./create-africode.js');
334
+ await createProject(args[1]);
335
+ break;
336
+ case 'help':
337
+ case '--help':
338
+ case '-h':
339
+ help();
340
+ break;
341
+ default:
342
+ help();
343
+ }
package/bin/cli.ts ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * AfriCode CLI v5.0.0 - Bun-Native Framework Tool
4
+ * Autonomous application lifecycle management
5
+ */
6
+
7
+ import '../core/shim.js';
8
+ import { help } from '../core/cli/ui.js';
9
+
10
+ const args = process.argv.slice(2);
11
+ const command = args[0];
12
+
13
+ async function run(): Promise<void> {
14
+ switch (command) {
15
+ case 'dev': {
16
+ const { dev } = await import('../core/cli/commands/dev.js');
17
+ await dev();
18
+ break;
19
+ }
20
+ case 'build': {
21
+ const { build } = await import('../core/cli/commands/build.js');
22
+ await build();
23
+ break;
24
+ }
25
+ case 'add': {
26
+ const { addComponent } = await import('../core/cli/commands/add.js');
27
+ await addComponent(args[1]);
28
+ break;
29
+ }
30
+ case 'migrate': {
31
+ const { runMigrations } = await import('../core/cli/migrator.js');
32
+ await runMigrations();
33
+ break;
34
+ }
35
+ case 'new':
36
+ case 'create':
37
+ case 'init': {
38
+ if (args[1] === 'plugin') {
39
+ const { createPlugin } = await import('../core/cli/commands/create-plugin.js');
40
+ await createPlugin(args[2], args.slice(3));
41
+ } else {
42
+ const { createProject } = await import('./scaffold.js');
43
+ await createProject(args[1]);
44
+ }
45
+ break;
46
+ }
47
+ case 'lint': {
48
+ const { lint } = await import('../core/cli/commands/lint.js');
49
+ await lint();
50
+ break;
51
+ }
52
+ case 'audit': {
53
+ const { audit } = await import('../core/cli/commands/audit.js');
54
+ await audit();
55
+ break;
56
+ }
57
+ case 'deploy': {
58
+ const { deploy } = await import('../core/cli/commands/deploy.js');
59
+ await deploy();
60
+ break;
61
+ }
62
+ case 'generate': {
63
+ if (args[1] === 'a2ui') {
64
+ const { generateA2UISchema } = await import('../core/a2ui-schema-manager.js');
65
+ await generateA2UISchema();
66
+ } else if (args[1] === 'patterns') {
67
+ const { generatePatterns } = await import('../core/patterns.js');
68
+ await generatePatterns();
69
+ }
70
+ break;
71
+ }
72
+ case 'help':
73
+ default: {
74
+ help();
75
+ break;
76
+ }
77
+ }
78
+ }
79
+
80
+ run().catch((error) => {
81
+ console.error('CLI Error:', error.message);
82
+ process.exit(1);
83
+ });
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * create-africode
4
+ *
5
+ * Scaffolds a new AfriCode project by copying the live framework files.
6
+ * usage: bunx create-africode my-app
7
+ *
8
+ * @module bin/create-africode
9
+ */
10
+
11
+ import { mkdir, writeFile, cp, readFile } from 'fs/promises';
12
+ import { existsSync } from 'fs';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const FRAMEWORK_ROOT = path.resolve(__dirname, '..');
19
+
20
+ const colors = {
21
+ green: '\x1b[32m',
22
+ gold: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ red: '\x1b[31m',
25
+ reset: '\x1b[0m',
26
+ bold: '\x1b[1m',
27
+ dim: '\x1b[2m'
28
+ };
29
+
30
+ export async function createProject(targetName) {
31
+ const projectName = targetName || 'africode-app';
32
+
33
+ console.log(`
34
+ ${colors.gold}╔═══════════════════════════════════════╗
35
+ ║ ${colors.green}Creating AfriCode Project${colors.gold} ║
36
+ ╚═══════════════════════════════════════╝${colors.reset}
37
+ `);
38
+
39
+ const projectDir = path.join(process.cwd(), projectName);
40
+
41
+ if (existsSync(projectDir)) {
42
+ console.log(`${colors.red}✗ Directory "${projectName}" already exists${colors.reset}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ console.log(`${colors.dim} Creating ${projectName}...${colors.reset}\n`);
47
+
48
+ try {
49
+ // 1. Create Project Root
50
+ await mkdir(projectDir);
51
+
52
+ // 2. Copy Template Files
53
+ // Support --template arg
54
+ // node bin/create-africode.js my-app --template 3d
55
+ const args = process.argv;
56
+ const templateIdx = args.indexOf('--template');
57
+ const templateName = templateIdx > -1 ? args[templateIdx + 1] : 'starter';
58
+
59
+ // Validate template
60
+ const validTemplates = ['starter', 'starter-3d', 'starter-tailwind', 'starter-react', '3d', 'tailwind', 'react'];
61
+ // Map short aliases to full template directory names
62
+ const aliasMap = { '3d': 'starter-3d', 'tailwind': 'starter-tailwind', 'react': 'starter-react' };
63
+ const actualTemplate = aliasMap[templateName] || templateName || 'starter';
64
+
65
+ const templateDir = path.join(FRAMEWORK_ROOT, 'templates', actualTemplate);
66
+
67
+ if (!existsSync(templateDir)) {
68
+ throw new Error(`Template "${actualTemplate}" not found. Available: starter, tailwind, react, 3d`);
69
+ }
70
+
71
+ console.log(`${colors.blue}ℹ Using template: ${actualTemplate}${colors.reset}`);
72
+
73
+ // Recursive copy of the template
74
+ await cp(templateDir, projectDir, { recursive: true });
75
+
76
+
77
+ // 3. Package.json is already copied from template
78
+ // We just need to update the name
79
+ const pkgPath = path.join(projectDir, 'package.json');
80
+ const pkgData = await readFile(pkgPath, 'utf8');
81
+ const pkgJson = JSON.parse(pkgData);
82
+
83
+ pkgJson.name = projectName;
84
+
85
+ await writeFile(pkgPath, JSON.stringify(pkgJson, null, 2));
86
+
87
+ // 4. Create bin directory and copy CLI tool (africode.js)
88
+ // This allows 'bun run bin/africode.js dev' to work in the new project
89
+ const binSrc = path.join(FRAMEWORK_ROOT, 'bin', 'africode.js');
90
+ const binDestDir = path.join(projectDir, 'bin');
91
+ await mkdir(binDestDir, { recursive: true });
92
+
93
+ if (existsSync(binSrc)) {
94
+ await cp(binSrc, path.join(binDestDir, 'africode.js'));
95
+ } else {
96
+ console.error(`${colors.red}✗ Critical: CLI entry point not found${colors.reset}`);
97
+ }
98
+
99
+ // 5. Auto-generate index.js if missing
100
+ const srcDir = path.join(projectDir, 'src');
101
+ await mkdir(srcDir, { recursive: true });
102
+ const indexPath = path.join(srcDir, 'index.js');
103
+ if (!existsSync(indexPath)) {
104
+ const indexContent = `/**
105
+ * AfriCode App Entry Point
106
+ *
107
+ * This file exports your app. Modify as needed for your use case:
108
+ * - Server-side rendering: import Layout from core
109
+ * - Static site: export your components here
110
+ * - SPA: import and initialize your router here
111
+ */
112
+
113
+ import { Layout } from './core/server/render.js';
114
+ import { html } from './core/html.js';
115
+
116
+ // Example: Simple HTML page export
117
+ export default function App() {
118
+ return Layout({
119
+ title: 'Welcome to AfriCode',
120
+ children: html\`
121
+ <main>
122
+ <h1>Welcome to AfriCode!</h1>
123
+ <p>Your African-centric full-stack framework is ready.</p>
124
+ <p><a href="/components">View Components</a> | <a href="/docs">Read Docs</a></p>
125
+ </main>
126
+ \`
127
+ });
128
+ }
129
+ `;
130
+ await writeFile(indexPath, indexContent);
131
+ console.log(`${colors.blue}ℹ Generated index.js entry point${colors.reset}`);
132
+ }
133
+
134
+ // 6. Success Message
135
+ console.log(`${colors.green}✓ Project created successfully!${colors.reset}\n`);
136
+ console.log(`${colors.bold}Your project contains:${colors.reset}`);
137
+ console.log(` ${colors.blue}africode.config.js${colors.reset} - Framework Configuration`);
138
+ console.log(` ${colors.blue}src/components/${colors.reset} - 24+ UI Components`);
139
+ console.log(` ${colors.blue}src/styles/${colors.reset} - Design Tokens`);
140
+ console.log(` ${colors.blue}src/pages/${colors.reset} - Documentation & Demos`);
141
+ console.log(` ${colors.blue}src/index.js${colors.reset} - App Entry Point (auto-generated)`);
142
+ console.log(`\n${colors.bold}Next steps:${colors.reset}`);
143
+ console.log(` ${colors.dim}cd${colors.reset} ${projectName}`);
144
+ console.log(` ${colors.dim}bun install${colors.reset} (if needed)`);
145
+ console.log(` ${colors.dim}bun run${colors.reset} dev\n`);
146
+ console.log(`${colors.gold}Happy coding with the rhythm of the continent!${colors.reset}\n`);
147
+
148
+ } catch (err) {
149
+ console.error(`${colors.red}✗ Failed to create project:${colors.reset}`, err);
150
+ process.exit(1);
151
+ }
152
+ }
153
+
154
+ // Allow standalone execution if run directly
155
+ if (import.meta.main) {
156
+ const args = process.argv.slice(2);
157
+ createProject(args[0]);
158
+ }