create-aziz-app 3.1.1 → 3.1.3

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 (33) hide show
  1. package/README.md +658 -207
  2. package/bin/create-aziz-app.js +302 -351
  3. package/package.json +1 -1
  4. package/template/README.md +14 -279
  5. package/template/_framework/api.js +219 -219
  6. package/template/_framework/boot.js +33 -33
  7. package/template/_framework/hooks.js +367 -291
  8. package/template/_framework/next-response.js +128 -128
  9. package/template/_framework/route-match.js +145 -145
  10. package/template/_framework/router.js +465 -259
  11. package/template/_framework/runtime.js +264 -264
  12. package/template/app/layout.js +3 -5
  13. package/template/app/page.js +9 -32
  14. package/template/docs/01-getting-started.md +151 -151
  15. package/template/docs/02-project-structure.md +109 -109
  16. package/template/docs/03-pages-and-layouts.md +140 -140
  17. package/template/docs/04-routing.md +120 -120
  18. package/template/docs/05-hooks.md +169 -169
  19. package/template/docs/06-api-routes.md +210 -210
  20. package/template/docs/07-api-client.md +151 -151
  21. package/template/docs/08-styling.md +94 -94
  22. package/template/docs/09-imports-and-modules.md +97 -97
  23. package/template/docs/10-database-and-models.md +154 -154
  24. package/template/docs/11-authentication.md +171 -171
  25. package/template/docs/12-production-and-deployment.md +121 -121
  26. package/template/docs/13-internals.md +179 -179
  27. package/template/docs/README.md +67 -67
  28. package/template/index.html +20 -20
  29. package/template/package.json +16 -16
  30. package/template/scripts/dev-server.js +493 -381
  31. package/template/scripts/lib/api-router.js +525 -525
  32. package/template/scripts/lib/js-transform.js +213 -141
  33. package/template/scripts/lib/module-resolver.js +132 -132
@@ -1,351 +1,302 @@
1
- #!/usr/bin/env node
2
-
3
- // ============================================================
4
- // create-aziz-app — project scaffolder for Aziz.js
5
- //
6
- // Usage:
7
- // npx create-aziz-app my-app
8
- // npx create-aziz-app my-app --css tailwind --no-docs
9
- // npx create-aziz-app my-app -y --port 4000
10
- //
11
- // Options:
12
- // -y, --yes Accept all defaults (no prompts)
13
- // --port <n> Dev-server port (default 3000)
14
- // --css <name> none | bootstrap | tailwind
15
- // --no-examples Skip the example Test page
16
- // --no-api Skip example API routes
17
- // --no-docs Skip the docs/ folder
18
- // -h, --help Show help
19
- // ============================================================
20
-
21
- const fs = require('fs');
22
- const path = require('path');
23
- const readline = require('readline');
24
-
25
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
26
-
27
- function question(query) {
28
- return new Promise(resolve => rl.question(query, resolve));
29
- }
30
-
31
- function parseArgs(argv) {
32
- const opts = {
33
- projectName: null,
34
- yes: false,
35
- port: null,
36
- css: null,
37
- examples: null,
38
- api: null,
39
- docs: null,
40
- help: false,
41
- };
42
-
43
- for (let i = 0; i < argv.length; i++) {
44
- const arg = argv[i];
45
- switch (arg) {
46
- case '-h':
47
- case '--help': opts.help = true; break;
48
- case '-y':
49
- case '--yes': opts.yes = true; break;
50
- case '--port': opts.port = argv[++i]; break;
51
- case '--css': opts.css = argv[++i]; break;
52
- case '--no-examples': opts.examples = false; break;
53
- case '--no-api': opts.api = false; break;
54
- case '--no-docs': opts.docs = false; break;
55
- default:
56
- if (!arg.startsWith('-') && !opts.projectName) opts.projectName = arg;
57
- }
58
- }
59
- return opts;
60
- }
61
-
62
- function printHelp() {
63
- console.log(`
64
- ⚡ create-aziz-app — scaffold a new Aziz.js project
65
-
66
- Usage:
67
- npx create-aziz-app <project-name> [options]
68
-
69
- Options:
70
- -y, --yes Accept all defaults (skip prompts)
71
- --port <n> Dev-server port (default 3000)
72
- --css <name> none | bootstrap | tailwind
73
- --no-examples Skip the example Test page
74
- --no-api Skip example API routes
75
- --no-docs Skip the docs/ folder
76
- -h, --help Show this help
77
-
78
- Examples:
79
- npx create-aziz-app blog
80
- npx create-aziz-app shop --css tailwind --port 4000
81
- `);
82
- }
83
-
84
- // Files/folders never copied into a new project.
85
- function shouldSkip(relPath) {
86
- if (relPath === 'node_modules' || relPath.startsWith('node_modules' + path.sep)) return true;
87
- if (relPath === '.DS_Store' || relPath.endsWith('.log')) return true;
88
- return false;
89
- }
90
-
91
- function copyDir(src, dest, features) {
92
- fs.mkdirSync(dest, { recursive: true });
93
-
94
- for (const entry of fs.readdirSync(src)) {
95
- const srcPath = path.join(src, entry);
96
- const destPath = path.join(dest, entry);
97
- const rel = dest; // not needed; handled below
98
-
99
- if (shouldSkip(entry)) continue;
100
-
101
- const isDir = fs.statSync(srcPath).isDirectory();
102
-
103
- // Docs are optional
104
- if (!features.docs && entry === 'docs' && isDir) continue;
105
-
106
- if (isDir) {
107
- copyDir(srcPath, destPath, features);
108
- } else {
109
- fs.copyFileSync(srcPath, destPath);
110
- }
111
- }
112
- }
113
-
114
- function removeDirIfExists(dir) {
115
- if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
116
- }
117
-
118
- function emptyDirContents(dir) {
119
- if (!fs.existsSync(dir)) return;
120
- for (const entry of fs.readdirSync(dir)) {
121
- fs.rmSync(path.join(dir, entry), { recursive: true, force: true });
122
- }
123
- }
124
-
125
- function updatePackageJson(targetDir, projectName) {
126
- const pkgPath = path.join(targetDir, 'package.json');
127
- if (!fs.existsSync(pkgPath)) return;
128
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
129
- pkg.name = projectName;
130
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
131
- }
132
-
133
- function updateDevServerPort(targetDir, port) {
134
- const devServerPath = path.join(targetDir, 'scripts', 'dev-server.js');
135
- if (!fs.existsSync(devServerPath)) return;
136
- let content = fs.readFileSync(devServerPath, 'utf8');
137
- content = content.replace(
138
- /const PORT = parseInt\(process\.env\.PORT, 10\) \|\| 3000;/,
139
- `const PORT = parseInt(process.env.PORT, 10) || ${port};`
140
- );
141
- fs.writeFileSync(devServerPath, content);
142
- }
143
-
144
- function updateTitle(targetDir, projectName) {
145
- const htmlPath = path.join(targetDir, 'index.html');
146
- if (!fs.existsSync(htmlPath)) return;
147
- let html = fs.readFileSync(htmlPath, 'utf8');
148
- const pretty = projectName.replace(/[-_]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
149
- html = html.replace(/<title>.*?<\/title>/, `<title>${pretty}</title>`);
150
- fs.writeFileSync(htmlPath, html);
151
- }
152
-
153
- function injectHead(html, snippet) {
154
- return html.replace('</head>', `${snippet}\n</head>`);
155
- }
156
-
157
- function injectBody(html, snippet) {
158
- return html.replace('</body>', `${snippet}\n</body>`);
159
- }
160
-
161
- function setupCSSFramework(targetDir, framework) {
162
- const htmlPath = path.join(targetDir, 'index.html');
163
- if (!fs.existsSync(htmlPath)) return;
164
-
165
- let html = fs.readFileSync(htmlPath, 'utf8');
166
-
167
- if (framework === 'bootstrap') {
168
- html = injectHead(html, ` <!-- Bootstrap 5 -->
169
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
170
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">`);
171
- html = injectBody(html, ` <!-- Bootstrap 5 JS -->
172
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>`);
173
- } else if (framework === 'tailwind') {
174
- html = injectHead(html, ` <!-- Tailwind CSS -->
175
- <script src="https://cdn.tailwindcss.com"></script>
176
- <script>
177
- tailwind.config = {
178
- theme: {
179
- extend: {
180
- colors: { primary: '#61dafb', dark: '#282c34' }
181
- }
182
- }
183
- }
184
- </script>`);
185
- }
186
- // 'none' → keep globals.css only
187
-
188
- fs.writeFileSync(htmlPath, html);
189
- }
190
-
191
- async function main() {
192
- const opts = parseArgs(process.argv.slice(2));
193
-
194
- if (opts.help) {
195
- printHelp();
196
- rl.close();
197
- return;
198
- }
199
-
200
- console.log(`\n⚡ Welcome to Aziz.js!\n`);
201
- console.log(`🎨 A lightweight Next.js clone — file-based routing, React hooks, API routes\n`);
202
-
203
- let projectName = opts.projectName;
204
- if (!projectName) {
205
- projectName = (await question('📁 Project name (my-aziz-js-app): ')).trim() || 'my-aziz-js-app';
206
- }
207
-
208
- if (!/^[a-z0-9-_]+$/i.test(projectName)) {
209
- console.error(`❌ Invalid project name. Use only letters, numbers, hyphens, and underscores.`);
210
- rl.close();
211
- process.exit(1);
212
- }
213
-
214
- const targetDir = path.join(process.cwd(), projectName);
215
-
216
- if (fs.existsSync(targetDir)) {
217
- let overwrite = 'y';
218
- if (!opts.yes) {
219
- overwrite = await question(`⚠️ Folder "${projectName}" already exists. Overwrite? (y/N): `);
220
- }
221
- if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
222
- console.log('❌ Cancelled.');
223
- rl.close();
224
- process.exit(0);
225
- }
226
- fs.rmSync(targetDir, { recursive: true, force: true });
227
- }
228
-
229
- // ── Port
230
- let port = opts.port;
231
- if (!port && !opts.yes) port = (await question('🌐 Dev server port (3000): ')).trim();
232
- port = port || '3000';
233
- if (!/^\d+$/.test(port)) {
234
- console.log('⚠️ Invalid port, using default 3000');
235
- port = '3000';
236
- }
237
-
238
- // ── Features
239
- let examples = opts.examples;
240
- let apiExamples = opts.api;
241
- let docs = opts.docs;
242
-
243
- if (!opts.yes) {
244
- if (examples === null) {
245
- const a = await question(' Include example pages? (Y/n): ');
246
- examples = !['n', 'no'].includes(a.trim().toLowerCase());
247
- }
248
- if (apiExamples === null) {
249
- const a = await question(' Include example API routes? (Y/n): ');
250
- apiExamples = !['n', 'no'].includes(a.trim().toLowerCase());
251
- }
252
- if (docs === null) {
253
- const a = await question(' Include documentation? (Y/n): ');
254
- docs = !['n', 'no'].includes(a.trim().toLowerCase());
255
- }
256
- }
257
- if (examples === null) examples = true;
258
- if (apiExamples === null) apiExamples = true;
259
- if (docs === null) docs = true;
260
-
261
- // ── CSS framework
262
- let css = opts.css;
263
- if (!css && !opts.yes) {
264
- console.log('\n🎨 CSS Framework:');
265
- console.log(' 1. None (custom CSS)');
266
- console.log(' 2. Bootstrap 5');
267
- console.log(' 3. Tailwind CSS');
268
- const choice = (await question(' Choose (1/2/3): ')).trim();
269
- css = choice === '2' ? 'bootstrap' : choice === '3' ? 'tailwind' : 'none';
270
- }
271
- if (!['none', 'bootstrap', 'tailwind'].includes(css)) css = 'none';
272
-
273
- const features = { examples, api: apiExamples, docs, css };
274
-
275
- console.log(`\n⚡ Creating "${projectName}"...\n`);
276
-
277
- const templateDir = path.join(__dirname, '..', 'template');
278
- if (!fs.existsSync(templateDir)) {
279
- console.error('❌ Template folder not found. Reinstall create-aziz-app.');
280
- rl.close();
281
- process.exit(1);
282
- }
283
-
284
- // 1. Copy the template (excluding node_modules)
285
- copyDir(templateDir, targetDir, features);
286
-
287
- // 2. Optional features
288
- if (!features.examples) {
289
- removeDirIfExists(path.join(targetDir, 'app', 'test'));
290
- }
291
- if (!features.api) {
292
- emptyDirContents(path.join(targetDir, 'api'));
293
- fs.mkdirSync(path.join(targetDir, 'api'), { recursive: true });
294
- }
295
-
296
- // 3. Personalize
297
- updatePackageJson(targetDir, projectName);
298
- updateDevServerPort(targetDir, port);
299
- updateTitle(targetDir, projectName);
300
- setupCSSFramework(targetDir, features.css);
301
-
302
- // 4. .gitignore
303
- fs.writeFileSync(path.join(targetDir, '.gitignore'), [
304
- 'node_modules/',
305
- '.DS_Store',
306
- '*.log',
307
- '',
308
- ].join('\n'));
309
-
310
- console.log(`✅ Done! Your project is ready.\n`);
311
- console.log(`📁 Created project: ${projectName}`);
312
- console.log(`🎨 CSS framework: ${features.css}`);
313
- console.log(`\n👉 Get started:\n`);
314
- console.log(` cd ${projectName}`);
315
- console.log(` npm install`);
316
- console.log(` npm run dev\n`);
317
- console.log(`🌐 Dev server: http://localhost:${port}/\n`);
318
- console.log(`📂 Project structure:\n`);
319
- console.log(` index.html ← HTML entry point`);
320
- console.log(` app/ ← pages + layouts (file-based routing)`);
321
- console.log(` layout.js ← root layout`);
322
- console.log(` page.js ← route: /`);
323
- console.log(` not-found.js ← custom 404`);
324
- console.log(` api/ ← server-side API route handlers`);
325
- console.log(` components/ ← shared UI components`);
326
- console.log(` lib/ models/ ← helpers + data models`);
327
- console.log(` _framework/ ← runtime, hooks, router, api client`);
328
- console.log(` scripts/ ← dev server + live reload`);
329
- if (features.docs) console.log(` docs/ ← full documentation`);
330
- console.log(`\n✨ Highlights:\n`);
331
- console.log(` • File-based routing app/about/page.js → /about`);
332
- console.log(` • Dynamic routes app/blog/[slug]/page.js → /blog/:slug`);
333
- console.log(` • Nested layouts app/**/layout.js`);
334
- console.log(` • React hooks useState, useEffect, useMemo, context…`);
335
- console.log(` • API route handlers api/*.js and app/api/**/route.js`);
336
- console.log(` • API client api.get('/api/users') — retries + timeouts`);
337
- console.log(` • Live reload instant browser refresh on save`);
338
- console.log(`\n💡 Tips:\n`);
339
- console.log(` • Create app/MyPage/page.js → route /MyPage`);
340
- console.log(` • Create api/todos.js → route /api/todos`);
341
- if (features.docs) console.log(` • Read docs/README.md for the full guide`);
342
- console.log(`\n🚀 Happy building!\n`);
343
-
344
- rl.close();
345
- }
346
-
347
- main().catch(err => {
348
- console.error('❌ Error:', err);
349
- rl.close();
350
- process.exit(1);
351
- });
1
+ #!/usr/bin/env node
2
+
3
+ // ============================================================
4
+ // create-aziz-app — project scaffolder for Aziz.js
5
+ //
6
+ // Usage:
7
+ // npx create-aziz-app my-app
8
+ // npx create-aziz-app my-app --css tailwind --no-docs
9
+ // npx create-aziz-app my-app -y --port 4000
10
+ //
11
+ // Options:
12
+ // -y, --yes Accept all defaults (no prompts)
13
+ // --port <n> Dev-server port (default 3000)
14
+ // --css <name> none | bootstrap | tailwind
15
+ // --no-docs Skip the docs/ folder
16
+ // -h, --help Show help
17
+ // ============================================================
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const readline = require('readline');
22
+
23
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+
25
+ function question(query) {
26
+ return new Promise(resolve => rl.question(query, resolve));
27
+ }
28
+
29
+ function parseArgs(argv) {
30
+ const opts = {
31
+ projectName: null,
32
+ yes: false,
33
+ port: null,
34
+ css: null,
35
+ docs: null,
36
+ help: false,
37
+ };
38
+
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const arg = argv[i];
41
+ switch (arg) {
42
+ case '-h':
43
+ case '--help': opts.help = true; break;
44
+ case '-y':
45
+ case '--yes': opts.yes = true; break;
46
+ case '--port': opts.port = argv[++i]; break;
47
+ case '--css': opts.css = argv[++i]; break;
48
+ case '--no-docs': opts.docs = false; break;
49
+ default:
50
+ if (!arg.startsWith('-') && !opts.projectName) opts.projectName = arg;
51
+ }
52
+ }
53
+ return opts;
54
+ }
55
+
56
+ function printHelp() {
57
+ console.log(`
58
+ ⚡ create-aziz-app — scaffold a new Aziz.js project
59
+
60
+ Usage:
61
+ npx create-aziz-app <project-name> [options]
62
+
63
+ Options:
64
+ -y, --yes Accept all defaults (skip prompts)
65
+ --port <n> Dev-server port (default 3000)
66
+ --css <name> none | bootstrap | tailwind
67
+ --no-docs Skip the docs/ folder
68
+ -h, --help Show this help
69
+
70
+ Examples:
71
+ npx create-aziz-app blog
72
+ npx create-aziz-app shop --css tailwind --port 4000
73
+ `);
74
+ }
75
+
76
+ // Files/folders never copied into a new project.
77
+ function shouldSkip(relPath) {
78
+ if (relPath === 'node_modules' || relPath.startsWith('node_modules' + path.sep)) return true;
79
+ if (relPath === '.DS_Store' || relPath.endsWith('.log')) return true;
80
+ return false;
81
+ }
82
+
83
+ function copyDir(src, dest, features) {
84
+ fs.mkdirSync(dest, { recursive: true });
85
+
86
+ for (const entry of fs.readdirSync(src)) {
87
+ const srcPath = path.join(src, entry);
88
+ const destPath = path.join(dest, entry);
89
+
90
+ if (shouldSkip(entry)) continue;
91
+
92
+ const isDir = fs.statSync(srcPath).isDirectory();
93
+
94
+ // Docs are optional
95
+ if (!features.docs && entry === 'docs' && isDir) continue;
96
+
97
+ if (isDir) {
98
+ copyDir(srcPath, destPath, features);
99
+ } else {
100
+ fs.copyFileSync(srcPath, destPath);
101
+ }
102
+ }
103
+ }
104
+
105
+ function updatePackageJson(targetDir, projectName) {
106
+ const pkgPath = path.join(targetDir, 'package.json');
107
+ if (!fs.existsSync(pkgPath)) return;
108
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
109
+ pkg.name = projectName;
110
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
111
+ }
112
+
113
+ function updateDevServerPort(targetDir, port) {
114
+ const devServerPath = path.join(targetDir, 'scripts', 'dev-server.js');
115
+ if (!fs.existsSync(devServerPath)) return;
116
+ let content = fs.readFileSync(devServerPath, 'utf8');
117
+ content = content.replace(
118
+ /const PORT = parseInt\(process\.env\.PORT, 10\) \|\| 3000;/,
119
+ `const PORT = parseInt(process.env.PORT, 10) || ${port};`
120
+ );
121
+ fs.writeFileSync(devServerPath, content);
122
+ }
123
+
124
+ function updateTitle(targetDir, projectName) {
125
+ const htmlPath = path.join(targetDir, 'index.html');
126
+ if (!fs.existsSync(htmlPath)) return;
127
+ let html = fs.readFileSync(htmlPath, 'utf8');
128
+ const pretty = projectName.replace(/[-_]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
129
+ html = html.replace(/<title>.*?<\/title>/, `<title>${pretty}</title>`);
130
+ fs.writeFileSync(htmlPath, html);
131
+ }
132
+
133
+ function injectHead(html, snippet) {
134
+ return html.replace('</head>', `${snippet}\n</head>`);
135
+ }
136
+
137
+ function injectBody(html, snippet) {
138
+ return html.replace('</body>', `${snippet}\n</body>`);
139
+ }
140
+
141
+ function setupCSSFramework(targetDir, framework) {
142
+ const htmlPath = path.join(targetDir, 'index.html');
143
+ if (!fs.existsSync(htmlPath)) return;
144
+
145
+ let html = fs.readFileSync(htmlPath, 'utf8');
146
+
147
+ if (framework === 'bootstrap') {
148
+ html = injectHead(html, ` <!-- Bootstrap 5 -->
149
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
150
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">`);
151
+ html = injectBody(html, ` <!-- Bootstrap 5 JS -->
152
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>`);
153
+ } else if (framework === 'tailwind') {
154
+ html = injectHead(html, ` <!-- Tailwind CSS -->
155
+ <script src="https://cdn.tailwindcss.com"></script>
156
+ <script>
157
+ tailwind.config = {
158
+ theme: {
159
+ extend: {
160
+ colors: { primary: '#61dafb', dark: '#282c34' }
161
+ }
162
+ }
163
+ }
164
+ </script>`);
165
+ }
166
+ // 'none' → keep globals.css only
167
+
168
+ fs.writeFileSync(htmlPath, html);
169
+ }
170
+
171
+ async function main() {
172
+ const opts = parseArgs(process.argv.slice(2));
173
+
174
+ if (opts.help) {
175
+ printHelp();
176
+ rl.close();
177
+ return;
178
+ }
179
+
180
+ console.log(`\n⚡ Welcome to Aziz.js!\n`);
181
+ console.log(`🎨 A lightweight Next.js clone — file-based routing, React hooks, live reload\n`);
182
+
183
+ let projectName = opts.projectName;
184
+ if (!projectName) {
185
+ projectName = (await question('📁 Project name (my-aziz-js-app): ')).trim() || 'my-aziz-js-app';
186
+ }
187
+
188
+ if (!/^[a-z0-9-_]+$/i.test(projectName)) {
189
+ console.error(`❌ Invalid project name. Use only letters, numbers, hyphens, and underscores.`);
190
+ rl.close();
191
+ process.exit(1);
192
+ }
193
+
194
+ const targetDir = path.join(process.cwd(), projectName);
195
+
196
+ if (fs.existsSync(targetDir)) {
197
+ let overwrite = 'y';
198
+ if (!opts.yes) {
199
+ overwrite = await question(`⚠️ Folder "${projectName}" already exists. Overwrite? (y/N): `);
200
+ }
201
+ if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
202
+ console.log('❌ Cancelled.');
203
+ rl.close();
204
+ process.exit(0);
205
+ }
206
+ fs.rmSync(targetDir, { recursive: true, force: true });
207
+ }
208
+
209
+ // ── Port
210
+ let port = opts.port;
211
+ if (!port && !opts.yes) port = (await question('🌐 Dev server port (3000): ')).trim();
212
+ port = port || '3000';
213
+ if (!/^\d+$/.test(port)) {
214
+ console.log('⚠️ Invalid port, using default 3000');
215
+ port = '3000';
216
+ }
217
+
218
+ // ── Features
219
+ let docs = opts.docs;
220
+
221
+ if (!opts.yes && docs === null) {
222
+ const a = await question(' Include documentation? (Y/n): ');
223
+ docs = !['n', 'no'].includes(a.trim().toLowerCase());
224
+ }
225
+ if (docs === null) docs = true;
226
+
227
+ // ── CSS framework
228
+ let css = opts.css;
229
+ if (!css && !opts.yes) {
230
+ console.log('\n🎨 CSS Framework:');
231
+ console.log(' 1. None (custom CSS)');
232
+ console.log(' 2. Bootstrap 5');
233
+ console.log(' 3. Tailwind CSS');
234
+ const choice = (await question(' Choose (1/2/3): ')).trim();
235
+ css = choice === '2' ? 'bootstrap' : choice === '3' ? 'tailwind' : 'none';
236
+ }
237
+ if (!['none', 'bootstrap', 'tailwind'].includes(css)) css = 'none';
238
+
239
+ const features = { docs, css };
240
+
241
+ console.log(`\n⚡ Creating "${projectName}"...\n`);
242
+
243
+ const templateDir = path.join(__dirname, '..', 'template');
244
+ if (!fs.existsSync(templateDir)) {
245
+ console.error('❌ Template folder not found. Reinstall create-aziz-app.');
246
+ rl.close();
247
+ process.exit(1);
248
+ }
249
+
250
+ // 1. Copy the template (excluding node_modules)
251
+ copyDir(templateDir, targetDir, features);
252
+
253
+ // 2. Personalize
254
+ updatePackageJson(targetDir, projectName);
255
+ updateDevServerPort(targetDir, port);
256
+ updateTitle(targetDir, projectName);
257
+ setupCSSFramework(targetDir, features.css);
258
+
259
+ // 3. .gitignore
260
+ fs.writeFileSync(path.join(targetDir, '.gitignore'), [
261
+ 'node_modules/',
262
+ '.DS_Store',
263
+ '*.log',
264
+ '',
265
+ ].join('\n'));
266
+
267
+ console.log(`✅ Done! Your project is ready.\n`);
268
+ console.log(`📁 Created project: ${projectName}`);
269
+ console.log(`🎨 CSS framework: ${features.css}`);
270
+ console.log(`\n👉 Get started:\n`);
271
+ console.log(` cd ${projectName}`);
272
+ console.log(` npm install`);
273
+ console.log(` npm run dev\n`);
274
+ console.log(`🌐 Dev server: http://localhost:${port}/\n`);
275
+ console.log(`📂 Project structure:\n`);
276
+ console.log(` index.html ← HTML entry point`);
277
+ console.log(` app/ ← pages + layouts (file-based routing)`);
278
+ console.log(` layout.js ← root layout`);
279
+ console.log(` page.js ← route: /`);
280
+ console.log(` styles/ ← global CSS`);
281
+ console.log(` _framework/ ← runtime, hooks, router`);
282
+ console.log(` scripts/ ← dev server + live reload`);
283
+ if (features.docs) console.log(` docs/ ← full documentation`);
284
+ console.log(`\n✨ Highlights:\n`);
285
+ console.log(` • File-based routing app/about/page.js → /about`);
286
+ console.log(` • Dynamic routes app/blog/[slug]/page.js → /blog/:slug`);
287
+ console.log(` • Nested layouts app/**/layout.js`);
288
+ console.log(` • React hooks useState, useEffect, useMemo, context…`);
289
+ console.log(` • Live reload instant browser refresh on save`);
290
+ console.log(`\n💡 Tips:\n`);
291
+ console.log(` • Create app/MyPage/page.js → route /MyPage`);
292
+ if (features.docs) console.log(` • Read docs/README.md for the full guide`);
293
+ console.log(`\n🚀 Happy building!\n`);
294
+
295
+ rl.close();
296
+ }
297
+
298
+ main().catch(err => {
299
+ console.error('❌ Error:', err);
300
+ rl.close();
301
+ process.exit(1);
302
+ });