create-aziz-app 3.1.2 → 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.
- package/README.md +658 -207
- package/bin/create-aziz-app.js +302 -302
- package/package.json +1 -1
- package/template/README.md +402 -402
- package/template/_framework/api.js +219 -219
- package/template/_framework/boot.js +33 -33
- package/template/_framework/hooks.js +367 -291
- package/template/_framework/next-response.js +128 -128
- package/template/_framework/route-match.js +145 -145
- package/template/_framework/router.js +465 -259
- package/template/_framework/runtime.js +264 -264
- package/template/app/layout.js +18 -18
- package/template/app/page.js +33 -33
- package/template/docs/01-getting-started.md +151 -151
- package/template/docs/02-project-structure.md +109 -109
- package/template/docs/03-pages-and-layouts.md +140 -140
- package/template/docs/04-routing.md +120 -120
- package/template/docs/05-hooks.md +169 -169
- package/template/docs/06-api-routes.md +210 -210
- package/template/docs/07-api-client.md +151 -151
- package/template/docs/08-styling.md +94 -94
- package/template/docs/09-imports-and-modules.md +97 -97
- package/template/docs/10-database-and-models.md +154 -154
- package/template/docs/11-authentication.md +171 -171
- package/template/docs/12-production-and-deployment.md +121 -121
- package/template/docs/13-internals.md +179 -179
- package/template/docs/README.md +67 -67
- package/template/index.html +20 -20
- package/template/package.json +16 -16
- package/template/scripts/dev-server.js +493 -381
- package/template/scripts/lib/api-router.js +525 -525
- package/template/scripts/lib/js-transform.js +213 -195
- package/template/scripts/lib/module-resolver.js +132 -132
package/bin/create-aziz-app.js
CHANGED
|
@@ -1,302 +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-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
|
-
});
|
|
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
|
+
});
|