student-help 2.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.
- package/LICENSE +15 -0
- package/README.md +44 -0
- package/bin/student-help.js +14 -0
- package/cli.mjs +1415 -0
- package/commands/db.ts +59 -0
- package/commands/dev.ts +23 -0
- package/commands/generate/auth.ts +31 -0
- package/commands/generate/install.ts +34 -0
- package/commands/generate/resource.ts +184 -0
- package/commands/new.ts +179 -0
- package/package.json +62 -0
- package/templates/backend/controller.ts +41 -0
- package/templates/backend/dto.ts +20 -0
- package/templates/backend/module.ts +16 -0
- package/templates/backend/service.ts +52 -0
- package/templates/backend/student-help.controller.ts +332 -0
- package/templates/backend/student-help.module.ts +9 -0
- package/templates/backend/student-help.service.ts +697 -0
- package/templates/frontend/page.tsx +13 -0
package/cli.mjs
ADDED
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// Print beautiful Student Help banner
|
|
11
|
+
// ============================================
|
|
12
|
+
const printBanner = () => {
|
|
13
|
+
console.clear();
|
|
14
|
+
console.log(chalk.hex('#00D9FF').bold('\n'));
|
|
15
|
+
console.log(chalk.hex('#00D9FF').bold(' ███████╗████████╗██╗ ██╗██████╗ ███████╗███╗ ██╗████████╗'));
|
|
16
|
+
console.log(chalk.hex('#0099FF').bold(' ██╔════╝╚══██╔══╝██║ ██║██╔══██╗██╔════╝████╗ ██║╚══██╔══╝'));
|
|
17
|
+
console.log(chalk.hex('#0052FF').bold(' ███████╗ ██║ ██║ ██║██║ ██║█████╗ ██╔██╗ ██║ ██║'));
|
|
18
|
+
console.log(chalk.hex('#6600FF').bold(' ╚════██║ ██║ ██║ ██║██║ ██║██╔══╝ ██║╚██╗██║ ██║'));
|
|
19
|
+
console.log(chalk.hex('#FF00FF').bold(' ███████║ ██║ ╚██████╔╝██████╔╝███████╗██║ ╚████║ ██║'));
|
|
20
|
+
console.log(chalk.hex('#FF0066').bold(' ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝'));
|
|
21
|
+
console.log(chalk.hex('#FF00FF').bold('\n 🚀 HELP FRAMEWORK - Create Full-Stack Apps in Minutes\n'));
|
|
22
|
+
console.log(chalk.hex('#00D9FF').bold(' ' + '═'.repeat(58) + '\n'));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// Command: new <projectName>
|
|
27
|
+
// ============================================
|
|
28
|
+
const newCommand = async (projectName) => {
|
|
29
|
+
printBanner();
|
|
30
|
+
|
|
31
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
32
|
+
fs.ensureDirSync(projectPath);
|
|
33
|
+
|
|
34
|
+
console.log(chalk.cyan.bold('🎯 Creating your full-stack project...\n'));
|
|
35
|
+
|
|
36
|
+
// Backend
|
|
37
|
+
let spinner = ora({
|
|
38
|
+
text: 'Setting up NestJS backend',
|
|
39
|
+
spinner: 'dots3',
|
|
40
|
+
color: 'cyan'
|
|
41
|
+
}).start();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const tempBackendName = path.basename(projectPath) + '_backend_temp';
|
|
45
|
+
execSync(`npx @nestjs/cli new ${tempBackendName} --package-manager npm --skip-git 2>&1 > /dev/null`, {
|
|
46
|
+
stdio: 'ignore',
|
|
47
|
+
cwd: '/tmp'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const tempBackendPath = path.join('/tmp', tempBackendName);
|
|
51
|
+
const backendDir = path.join(projectPath, 'backend');
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(tempBackendPath)) {
|
|
54
|
+
throw new Error('Backend temporary directory was not created');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fs.moveSync(tempBackendPath, backendDir);
|
|
58
|
+
spinner.succeed(chalk.green('Backend NestJS configured'));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
spinner.fail(chalk.red('Error creating backend'));
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Frontend
|
|
65
|
+
spinner = ora({
|
|
66
|
+
text: 'Setting up Next.js frontend',
|
|
67
|
+
spinner: 'dots3',
|
|
68
|
+
color: 'magenta'
|
|
69
|
+
}).start();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
execSync(`npx create-next-app@latest ${projectPath}/frontend --typescript --use-npm --eslint --app --no-src-dir --import-alias '@/*' --skip-install 2>&1 > /dev/null`, {
|
|
73
|
+
stdio: 'ignore',
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
env: {
|
|
76
|
+
...process.env,
|
|
77
|
+
CI: 'true'
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
spinner.succeed(chalk.green('Frontend structure created'));
|
|
81
|
+
|
|
82
|
+
spinner = ora({
|
|
83
|
+
text: 'Installing frontend dependencies',
|
|
84
|
+
spinner: 'arc',
|
|
85
|
+
color: 'blue'
|
|
86
|
+
}).start();
|
|
87
|
+
|
|
88
|
+
execSync(`npm install`, { stdio: 'ignore', cwd: path.join(projectPath, 'frontend') });
|
|
89
|
+
spinner.succeed(chalk.green('Frontend dependencies installed'));
|
|
90
|
+
|
|
91
|
+
spinner = ora({
|
|
92
|
+
text: 'Adding modern packages (Tailwind, ESLint, Prettier)',
|
|
93
|
+
spinner: 'bouncingBar',
|
|
94
|
+
color: 'yellow'
|
|
95
|
+
}).start();
|
|
96
|
+
|
|
97
|
+
const frontendPath = path.join(projectPath, 'frontend');
|
|
98
|
+
execSync(`npm install -D tailwindcss postcss autoprefixer prettier @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-prettier axios zustand 2>&1 > /dev/null`, {
|
|
99
|
+
stdio: 'ignore',
|
|
100
|
+
cwd: frontendPath
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
spinner.succeed(chalk.green('Frontend configured with modern packages'));
|
|
104
|
+
} catch (error) {
|
|
105
|
+
spinner.fail(chalk.red('Error creating frontend'));
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Docker configuration
|
|
110
|
+
spinner = ora({
|
|
111
|
+
text: 'Setting up Docker & environment',
|
|
112
|
+
spinner: 'line',
|
|
113
|
+
color: 'cyan'
|
|
114
|
+
}).start();
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const backendDir = path.join(projectPath, 'backend');
|
|
118
|
+
const frontendDir = path.join(projectPath, 'frontend');
|
|
119
|
+
|
|
120
|
+
fs.ensureDirSync(backendDir);
|
|
121
|
+
fs.ensureDirSync(frontendDir);
|
|
122
|
+
|
|
123
|
+
// Backend Dockerfile
|
|
124
|
+
const backendDockerfile = [
|
|
125
|
+
'FROM node:18-alpine',
|
|
126
|
+
'',
|
|
127
|
+
'WORKDIR /app',
|
|
128
|
+
'',
|
|
129
|
+
'COPY package*.json ./',
|
|
130
|
+
'',
|
|
131
|
+
'RUN npm ci',
|
|
132
|
+
'',
|
|
133
|
+
'COPY . .',
|
|
134
|
+
'',
|
|
135
|
+
'RUN npm run build',
|
|
136
|
+
'',
|
|
137
|
+
'EXPOSE 3000',
|
|
138
|
+
'',
|
|
139
|
+
'CMD ["node", "dist/main.js"]',
|
|
140
|
+
].join('\n');
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(path.join(backendDir, 'Dockerfile'), backendDockerfile);
|
|
143
|
+
|
|
144
|
+
// Frontend Dockerfile
|
|
145
|
+
const frontendDockerfile = [
|
|
146
|
+
'FROM node:18-alpine',
|
|
147
|
+
'',
|
|
148
|
+
'WORKDIR /app',
|
|
149
|
+
'',
|
|
150
|
+
'COPY package*.json ./',
|
|
151
|
+
'',
|
|
152
|
+
'RUN npm ci',
|
|
153
|
+
'',
|
|
154
|
+
'COPY . .',
|
|
155
|
+
'',
|
|
156
|
+
'RUN npm run build',
|
|
157
|
+
'',
|
|
158
|
+
'EXPOSE 3000',
|
|
159
|
+
'',
|
|
160
|
+
'CMD ["npm", "start"]',
|
|
161
|
+
].join('\n');
|
|
162
|
+
|
|
163
|
+
fs.writeFileSync(path.join(frontendDir, 'Dockerfile'), frontendDockerfile);
|
|
164
|
+
|
|
165
|
+
// Docker Compose
|
|
166
|
+
const dockerCompose = `version: '3.8'
|
|
167
|
+
|
|
168
|
+
services:
|
|
169
|
+
backend:
|
|
170
|
+
build: ./backend
|
|
171
|
+
container_name: ${projectName}_backend
|
|
172
|
+
ports:
|
|
173
|
+
- "3000:3000"
|
|
174
|
+
environment:
|
|
175
|
+
NODE_ENV: development
|
|
176
|
+
DB_HOST: postgres
|
|
177
|
+
DB_PORT: 5432
|
|
178
|
+
DB_USER: student
|
|
179
|
+
DB_PASSWORD: student123
|
|
180
|
+
DB_NAME: ${projectName}
|
|
181
|
+
DATABASE_URL: postgresql://student:student123@postgres:5432/${projectName}
|
|
182
|
+
volumes:
|
|
183
|
+
- ./backend:/app
|
|
184
|
+
- /app/node_modules
|
|
185
|
+
depends_on:
|
|
186
|
+
postgres:
|
|
187
|
+
condition: service_healthy
|
|
188
|
+
networks:
|
|
189
|
+
- app-network
|
|
190
|
+
restart: unless-stopped
|
|
191
|
+
|
|
192
|
+
frontend:
|
|
193
|
+
build: ./frontend
|
|
194
|
+
container_name: ${projectName}_frontend
|
|
195
|
+
ports:
|
|
196
|
+
- "3001:3000"
|
|
197
|
+
environment:
|
|
198
|
+
NODE_ENV: development
|
|
199
|
+
NEXT_PUBLIC_API_URL: http://localhost:3000/api
|
|
200
|
+
volumes:
|
|
201
|
+
- ./frontend:/app
|
|
202
|
+
- /app/node_modules
|
|
203
|
+
depends_on:
|
|
204
|
+
- backend
|
|
205
|
+
networks:
|
|
206
|
+
- app-network
|
|
207
|
+
restart: unless-stopped
|
|
208
|
+
|
|
209
|
+
postgres:
|
|
210
|
+
image: postgres:16-alpine
|
|
211
|
+
container_name: ${projectName}_postgres
|
|
212
|
+
environment:
|
|
213
|
+
POSTGRES_USER: student
|
|
214
|
+
POSTGRES_PASSWORD: student123
|
|
215
|
+
POSTGRES_DB: ${projectName}
|
|
216
|
+
ports:
|
|
217
|
+
- "5432:5432"
|
|
218
|
+
volumes:
|
|
219
|
+
- postgres_data:/var/lib/postgresql/data
|
|
220
|
+
networks:
|
|
221
|
+
- app-network
|
|
222
|
+
healthcheck:
|
|
223
|
+
test: ["CMD-SHELL", "pg_isready -U student -d ${projectName}"]
|
|
224
|
+
interval: 10s
|
|
225
|
+
timeout: 5s
|
|
226
|
+
retries: 5
|
|
227
|
+
restart: unless-stopped
|
|
228
|
+
|
|
229
|
+
volumes:
|
|
230
|
+
postgres_data:
|
|
231
|
+
driver: local
|
|
232
|
+
|
|
233
|
+
networks:
|
|
234
|
+
app-network:
|
|
235
|
+
driver: bridge
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
fs.writeFileSync(path.join(projectPath, 'docker-compose.yml'), dockerCompose);
|
|
239
|
+
|
|
240
|
+
// .env.example
|
|
241
|
+
const envExample = `# Backend Configuration
|
|
242
|
+
BACKEND_PORT=3000
|
|
243
|
+
NODE_ENV=development
|
|
244
|
+
|
|
245
|
+
# Frontend Configuration
|
|
246
|
+
FRONTEND_PORT=3001
|
|
247
|
+
NEXT_PUBLIC_API_URL=http://localhost:3000/api
|
|
248
|
+
|
|
249
|
+
# Database Configuration
|
|
250
|
+
DB_HOST=postgres
|
|
251
|
+
DB_PORT=5432
|
|
252
|
+
DB_USER=student
|
|
253
|
+
DB_PASSWORD=student123
|
|
254
|
+
DB_NAME=${projectName}
|
|
255
|
+
DATABASE_URL=postgresql://student:student123@postgres:5432/${projectName}
|
|
256
|
+
|
|
257
|
+
# Security
|
|
258
|
+
JWT_SECRET=your_super_secret_key_change_this_in_production
|
|
259
|
+
JWT_EXPIRATION=7d
|
|
260
|
+
|
|
261
|
+
# API
|
|
262
|
+
API_PREFIX=api/v1
|
|
263
|
+
`;
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
|
|
266
|
+
|
|
267
|
+
// .gitignore
|
|
268
|
+
const gitignore = [
|
|
269
|
+
'# Dependencies',
|
|
270
|
+
'node_modules/',
|
|
271
|
+
'/.pnp',
|
|
272
|
+
'.pnp.js',
|
|
273
|
+
'',
|
|
274
|
+
'# Testing',
|
|
275
|
+
'/coverage',
|
|
276
|
+
'',
|
|
277
|
+
'# Production',
|
|
278
|
+
'/build',
|
|
279
|
+
'/dist',
|
|
280
|
+
'/out',
|
|
281
|
+
'',
|
|
282
|
+
'# Misc',
|
|
283
|
+
'.DS_Store',
|
|
284
|
+
'*.pem',
|
|
285
|
+
'',
|
|
286
|
+
'# Logs',
|
|
287
|
+
'npm-debug.log*',
|
|
288
|
+
'yarn-debug.log*',
|
|
289
|
+
'yarn-error.log*',
|
|
290
|
+
'pnpm-debug.log*',
|
|
291
|
+
'lerna-debug.log*',
|
|
292
|
+
'',
|
|
293
|
+
'# Environment',
|
|
294
|
+
'.env',
|
|
295
|
+
'.env.local',
|
|
296
|
+
'.env.development.local',
|
|
297
|
+
'.env.test.local',
|
|
298
|
+
'.env.production.local',
|
|
299
|
+
'',
|
|
300
|
+
'# IDE',
|
|
301
|
+
'.vscode',
|
|
302
|
+
'.idea',
|
|
303
|
+
'*.swp',
|
|
304
|
+
'*.swo',
|
|
305
|
+
'*~',
|
|
306
|
+
'.iml',
|
|
307
|
+
'',
|
|
308
|
+
'# OS',
|
|
309
|
+
'.AppleDouble',
|
|
310
|
+
'.LSOverride',
|
|
311
|
+
'Thumbs.db',
|
|
312
|
+
'',
|
|
313
|
+
'# Next.js',
|
|
314
|
+
'.next',
|
|
315
|
+
'out',
|
|
316
|
+
'',
|
|
317
|
+
'# Docker',
|
|
318
|
+
'docker-compose.override.yml',
|
|
319
|
+
].join('\n');
|
|
320
|
+
|
|
321
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
|
|
322
|
+
|
|
323
|
+
// .prettierrc.json
|
|
324
|
+
const prettierConfig = {
|
|
325
|
+
semi: true,
|
|
326
|
+
trailingComma: 'es5',
|
|
327
|
+
singleQuote: true,
|
|
328
|
+
printWidth: 100,
|
|
329
|
+
tabWidth: 2,
|
|
330
|
+
useTabs: false,
|
|
331
|
+
arrowParens: 'always',
|
|
332
|
+
endOfLine: 'lf'
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
fs.writeFileSync(path.join(projectPath, '.prettierrc.json'), JSON.stringify(prettierConfig, null, 2));
|
|
336
|
+
|
|
337
|
+
spinner.succeed(chalk.green('Docker & environment configured'));
|
|
338
|
+
} catch (error) {
|
|
339
|
+
spinner.fail(chalk.red('Error configuring Docker'));
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// README
|
|
344
|
+
spinner = ora({
|
|
345
|
+
text: 'Generating documentation',
|
|
346
|
+
spinner: 'pong',
|
|
347
|
+
color: 'green'
|
|
348
|
+
}).start();
|
|
349
|
+
|
|
350
|
+
const readmeContent = `# ${projectName}
|
|
351
|
+
|
|
352
|
+
A modern full-stack application created with **Student Help Framework**.
|
|
353
|
+
|
|
354
|
+
## 📚 What is Student Help Framework?
|
|
355
|
+
|
|
356
|
+
Student Help Framework is a mini-framework that simplifies the creation of modern full-stack applications. It automates the complete setup of:
|
|
357
|
+
|
|
358
|
+
- **Backend**: NestJS (a robust and scalable Node.js framework)
|
|
359
|
+
- **Frontend**: Next.js (React framework with SSR and optimizations)
|
|
360
|
+
- **Database**: PostgreSQL (reliable relational database)
|
|
361
|
+
- **Docker**: Complete containerization for environment isolation
|
|
362
|
+
- **DevOps**: Docker Compose for local orchestration
|
|
363
|
+
|
|
364
|
+
### Why use Student Help Framework?
|
|
365
|
+
|
|
366
|
+
✨ **Zero Configuration** - Everything is pre-configured and ready to go
|
|
367
|
+
⚡ **Production Ready** - Scalable structure from day one
|
|
368
|
+
🐳 **Docker Included** - Consistent environment across machines
|
|
369
|
+
🔄 **Code Generation** - Create CRUD resources with a single command
|
|
370
|
+
📦 **Modern Stack** - Latest technologies with best practices
|
|
371
|
+
🎨 **Professional UI** - Tailwind CSS pre-configured
|
|
372
|
+
|
|
373
|
+
## 🚀 Getting Started
|
|
374
|
+
|
|
375
|
+
### 1. Start the Development Environment
|
|
376
|
+
|
|
377
|
+
\`\`\`bash
|
|
378
|
+
docker-compose up -d
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
This starts:
|
|
382
|
+
- **Backend**: Port 3000
|
|
383
|
+
- **Frontend**: Port 3001
|
|
384
|
+
- **PostgreSQL**: Port 5432
|
|
385
|
+
|
|
386
|
+
### 2. Access the Application
|
|
387
|
+
|
|
388
|
+
- **Frontend**: http://localhost:3001
|
|
389
|
+
- **Backend API**: http://localhost:3000
|
|
390
|
+
- **Database**: localhost:5432 (user: student / password: student123)
|
|
391
|
+
|
|
392
|
+
## 🛠️ Development Without Docker
|
|
393
|
+
|
|
394
|
+
### Backend
|
|
395
|
+
\`\`\`bash
|
|
396
|
+
cd backend
|
|
397
|
+
npm install
|
|
398
|
+
npm run start:dev
|
|
399
|
+
\`\`\`
|
|
400
|
+
|
|
401
|
+
Server runs at: http://localhost:3000
|
|
402
|
+
|
|
403
|
+
### Frontend
|
|
404
|
+
\`\`\`bash
|
|
405
|
+
cd frontend
|
|
406
|
+
npm install
|
|
407
|
+
npm run dev
|
|
408
|
+
\`\`\`
|
|
409
|
+
|
|
410
|
+
App runs at: http://localhost:3001
|
|
411
|
+
|
|
412
|
+
## 📦 Generate Resources Automatically
|
|
413
|
+
|
|
414
|
+
### Create Authentication Module
|
|
415
|
+
\`\`\`bash
|
|
416
|
+
student-help generate:auth
|
|
417
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
### Create a CRUD Resource
|
|
420
|
+
\`\`\`bash
|
|
421
|
+
student-help generate:resource Post
|
|
422
|
+
\`\`\`
|
|
423
|
+
|
|
424
|
+
## 📁 Project Structure
|
|
425
|
+
|
|
426
|
+
\`\`\`
|
|
427
|
+
${projectName}/
|
|
428
|
+
├── backend/ # NestJS API
|
|
429
|
+
├── frontend/ # Next.js App
|
|
430
|
+
├── docker-compose.yml # Orchestration
|
|
431
|
+
├── .env.example # Environment template
|
|
432
|
+
└── .gitignore
|
|
433
|
+
\`\`\`
|
|
434
|
+
|
|
435
|
+
## 🗄️ Database
|
|
436
|
+
|
|
437
|
+
PostgreSQL is pre-configured:
|
|
438
|
+
- **User**: student
|
|
439
|
+
- **Password**: student123
|
|
440
|
+
- **Database**: ${projectName}
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
**Built with ❤️ using Student Help Framework**
|
|
445
|
+
`;
|
|
446
|
+
|
|
447
|
+
fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent);
|
|
448
|
+
|
|
449
|
+
spinner.succeed(chalk.green('Documentation generated'));
|
|
450
|
+
|
|
451
|
+
// Git initialization
|
|
452
|
+
spinner = ora({
|
|
453
|
+
text: 'Initializing Git repository',
|
|
454
|
+
spinner: 'dots2',
|
|
455
|
+
color: 'blue'
|
|
456
|
+
}).start();
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
execSync(`cd ${projectPath} && git init && git add . && git commit -m "Initial commit: Full-stack project setup with Student Help Framework" 2>&1 > /dev/null`, {
|
|
460
|
+
stdio: 'ignore',
|
|
461
|
+
cwd: projectPath
|
|
462
|
+
});
|
|
463
|
+
spinner.succeed(chalk.green('Git repository created'));
|
|
464
|
+
} catch (error) {
|
|
465
|
+
spinner.succeed(chalk.yellow('Git initialized'));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Add backend modern packages
|
|
469
|
+
spinner = ora({
|
|
470
|
+
text: 'Adding backend modern packages',
|
|
471
|
+
spinner: 'flip',
|
|
472
|
+
color: 'magenta'
|
|
473
|
+
}).start();
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
const backendPath = path.join(projectPath, 'backend');
|
|
477
|
+
execSync(`npm install @nestjs/config @nestjs/typeorm typeorm pg joi class-validator class-transformer helmet cors 2>&1 > /dev/null`, {
|
|
478
|
+
stdio: 'ignore',
|
|
479
|
+
cwd: backendPath
|
|
480
|
+
});
|
|
481
|
+
execSync(`npm install -D prettier @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-prettier 2>&1 > /dev/null`, {
|
|
482
|
+
stdio: 'ignore',
|
|
483
|
+
cwd: backendPath
|
|
484
|
+
});
|
|
485
|
+
spinner.succeed(chalk.green('Backend dependencies updated'));
|
|
486
|
+
} catch (error) {
|
|
487
|
+
spinner.succeed(chalk.yellow('Backend packages updated'));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Create .prettierrc for backend
|
|
491
|
+
const prettierConfig = {
|
|
492
|
+
semi: true,
|
|
493
|
+
trailingComma: 'es5',
|
|
494
|
+
singleQuote: true,
|
|
495
|
+
printWidth: 100,
|
|
496
|
+
tabWidth: 2,
|
|
497
|
+
useTabs: false,
|
|
498
|
+
arrowParens: 'always',
|
|
499
|
+
endOfLine: 'lf'
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
fs.writeFileSync(path.join(projectPath, 'backend', '.prettierrc.json'), JSON.stringify(prettierConfig, null, 2));
|
|
503
|
+
|
|
504
|
+
// Setup StudentHelp Module
|
|
505
|
+
spinner = ora({
|
|
506
|
+
text: 'Setting up StudentHelp utility module',
|
|
507
|
+
spinner: 'dots3',
|
|
508
|
+
color: 'cyan'
|
|
509
|
+
}).start();
|
|
510
|
+
|
|
511
|
+
try {
|
|
512
|
+
const backendPath = path.join(projectPath, 'backend');
|
|
513
|
+
const srcPath = path.join(backendPath, 'src');
|
|
514
|
+
const studentHelpDir = path.join(srcPath, 'student-help');
|
|
515
|
+
|
|
516
|
+
fs.ensureDirSync(studentHelpDir);
|
|
517
|
+
|
|
518
|
+
// Get template files path - resolve from current script location
|
|
519
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
520
|
+
const __dirname = path.dirname(__filename);
|
|
521
|
+
const templatesPath = path.join(__dirname, 'templates', 'backend');
|
|
522
|
+
|
|
523
|
+
// Read StudentHelp templates from disk
|
|
524
|
+
const serviceTemplatePath = path.join(templatesPath, 'student-help.service.ts');
|
|
525
|
+
const controllerTemplatePath = path.join(templatesPath, 'student-help.controller.ts');
|
|
526
|
+
const studentHelpModuleTemplate = `import { Global, Module } from '@nestjs/common';
|
|
527
|
+
import { StudentHelpService } from './student-help.service';
|
|
528
|
+
import { StudentHelpController } from './student-help.controller';
|
|
529
|
+
|
|
530
|
+
@Global()
|
|
531
|
+
@Module({
|
|
532
|
+
controllers: [StudentHelpController],
|
|
533
|
+
providers: [StudentHelpService],
|
|
534
|
+
exports: [StudentHelpService],
|
|
535
|
+
})
|
|
536
|
+
export class StudentHelpModule {}
|
|
537
|
+
`;
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
// Check if template files exist
|
|
541
|
+
if (!fs.existsSync(serviceTemplatePath)) {
|
|
542
|
+
throw new Error(`Service template not found at: ${serviceTemplatePath}`);
|
|
543
|
+
}
|
|
544
|
+
if (!fs.existsSync(controllerTemplatePath)) {
|
|
545
|
+
throw new Error(`Controller template not found at: ${controllerTemplatePath}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const studentHelpServiceTemplate = fs.readFileSync(serviceTemplatePath, 'utf-8');
|
|
549
|
+
const studentHelpControllerTemplate = fs.readFileSync(controllerTemplatePath, 'utf-8');
|
|
550
|
+
|
|
551
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.module.ts'), studentHelpModuleTemplate);
|
|
552
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.service.ts'), studentHelpServiceTemplate);
|
|
553
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.controller.ts'), studentHelpControllerTemplate);
|
|
554
|
+
} catch (templateError) {
|
|
555
|
+
// Fallback to basic version if templates don't exist
|
|
556
|
+
const fallbackService = `import { Injectable } from '@nestjs/common';
|
|
557
|
+
import * as bcrypt from 'bcrypt';
|
|
558
|
+
|
|
559
|
+
export interface StudentUser {
|
|
560
|
+
id?: string;
|
|
561
|
+
email: string;
|
|
562
|
+
name: string;
|
|
563
|
+
password?: string;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export interface AuthResponse {
|
|
567
|
+
success: boolean;
|
|
568
|
+
message: string;
|
|
569
|
+
token?: string;
|
|
570
|
+
user?: StudentUser;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
@Injectable()
|
|
574
|
+
export class StudentHelpService {
|
|
575
|
+
private users: Map<string, StudentUser> = new Map();
|
|
576
|
+
private sessions: Map<string, AuthResponse> = new Map();
|
|
577
|
+
|
|
578
|
+
async register(email: string, name: string, password: string): Promise<AuthResponse> {
|
|
579
|
+
try {
|
|
580
|
+
if (this._getUserByEmail(email)) {
|
|
581
|
+
return { success: false, message: 'User already exists' };
|
|
582
|
+
}
|
|
583
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
584
|
+
const userId = \`user_\${Date.now()}\`;
|
|
585
|
+
const user: StudentUser = { id: userId, email, name, password: hashedPassword };
|
|
586
|
+
this.users.set(userId, user);
|
|
587
|
+
return { success: true, message: 'User registered successfully', user: { id: userId, email, name } };
|
|
588
|
+
} catch (error) {
|
|
589
|
+
return { success: false, message: \`Registration failed: \${error.message}\` };
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async login(email: string, password: string): Promise<AuthResponse> {
|
|
594
|
+
try {
|
|
595
|
+
const user = this._getUserByEmail(email);
|
|
596
|
+
if (!user || !user.password) {
|
|
597
|
+
return { success: false, message: 'Invalid credentials' };
|
|
598
|
+
}
|
|
599
|
+
const isPasswordValid = await bcrypt.compare(password, user.password);
|
|
600
|
+
if (!isPasswordValid) {
|
|
601
|
+
return { success: false, message: 'Invalid credentials' };
|
|
602
|
+
}
|
|
603
|
+
const response: AuthResponse = { success: true, message: 'Login successful', user: { id: user.id, email: user.email, name: user.name } };
|
|
604
|
+
this.sessions.set(user.id || '', response);
|
|
605
|
+
return response;
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return { success: false, message: \`Login failed: \${error.message}\` };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async logout(userId: string): Promise<AuthResponse> {
|
|
612
|
+
try {
|
|
613
|
+
this.sessions.delete(userId);
|
|
614
|
+
return { success: true, message: 'Logout successful' };
|
|
615
|
+
} catch (error) {
|
|
616
|
+
return { success: false, message: \`Logout failed: \${error.message}\` };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
getUser(userId: string): StudentUser | null {
|
|
621
|
+
const user = this.users.get(userId);
|
|
622
|
+
if (user) {
|
|
623
|
+
const { password, ...userWithoutPassword } = user;
|
|
624
|
+
return userWithoutPassword as StudentUser;
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
getAllUsers(): StudentUser[] {
|
|
630
|
+
const users: StudentUser[] = [];
|
|
631
|
+
this.users.forEach((user) => {
|
|
632
|
+
const { password, ...userWithoutPassword } = user;
|
|
633
|
+
users.push(userWithoutPassword as StudentUser);
|
|
634
|
+
});
|
|
635
|
+
return users;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async updateUser(userId: string, updateData: Partial<StudentUser>): Promise<AuthResponse> {
|
|
639
|
+
try {
|
|
640
|
+
const user = this.users.get(userId);
|
|
641
|
+
if (!user) {
|
|
642
|
+
return { success: false, message: 'User not found' };
|
|
643
|
+
}
|
|
644
|
+
if (updateData.name) user.name = updateData.name;
|
|
645
|
+
if (updateData.email) user.email = updateData.email;
|
|
646
|
+
return { success: true, message: 'User updated successfully', user: { id: user.id, email: user.email, name: user.name } };
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return { success: false, message: \`Update failed: \${error.message}\` };
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async deleteUser(userId: string): Promise<AuthResponse> {
|
|
653
|
+
try {
|
|
654
|
+
const user = this.users.get(userId);
|
|
655
|
+
if (!user) {
|
|
656
|
+
return { success: false, message: 'User not found' };
|
|
657
|
+
}
|
|
658
|
+
this.users.delete(userId);
|
|
659
|
+
this.sessions.delete(userId);
|
|
660
|
+
return { success: true, message: 'User deleted successfully' };
|
|
661
|
+
} catch (error) {
|
|
662
|
+
return { success: false, message: \`Delete failed: \${error.message}\` };
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
isAuthenticated(userId: string): boolean {
|
|
667
|
+
return this.sessions.has(userId);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private _getUserByEmail(email: string): StudentUser | null {
|
|
671
|
+
for (const user of this.users.values()) {
|
|
672
|
+
if (user.email === email) {
|
|
673
|
+
return user;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
getInfo() {
|
|
680
|
+
return { name: 'StudentHelp', version: '2.0.0', description: 'Global utility service for student applications' };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
`;
|
|
684
|
+
const fallbackController = `import { Controller, Post, Get, Body, Param } from '@nestjs/common';
|
|
685
|
+
import { StudentHelpService, AuthResponse } from './student-help.service';
|
|
686
|
+
|
|
687
|
+
@Controller('student-help')
|
|
688
|
+
export class StudentHelpController {
|
|
689
|
+
constructor(private studentHelpService: StudentHelpService) {}
|
|
690
|
+
|
|
691
|
+
@Post('register')
|
|
692
|
+
async register(@Body() body: { email: string; name: string; password: string }): Promise<AuthResponse> {
|
|
693
|
+
return this.studentHelpService.register(body.email, body.name, body.password);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
@Post('login')
|
|
697
|
+
async login(@Body() body: { email: string; password: string }): Promise<AuthResponse> {
|
|
698
|
+
return this.studentHelpService.login(body.email, body.password);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
@Post('logout/:userId')
|
|
702
|
+
async logout(@Param('userId') userId: string): Promise<AuthResponse> {
|
|
703
|
+
return this.studentHelpService.logout(userId);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@Get('user/:userId')
|
|
707
|
+
getUser(@Param('userId') userId: string) {
|
|
708
|
+
return this.studentHelpService.getUser(userId);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
@Get('users')
|
|
712
|
+
getAllUsers() {
|
|
713
|
+
return this.studentHelpService.getAllUsers();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
@Get('info')
|
|
717
|
+
getInfo() {
|
|
718
|
+
return this.studentHelpService.getInfo();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
`;
|
|
722
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.module.ts'), studentHelpModuleTemplate);
|
|
723
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.service.ts'), fallbackService);
|
|
724
|
+
fs.writeFileSync(path.join(studentHelpDir, 'student-help.controller.ts'), fallbackController);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Read and modify app.module.ts
|
|
728
|
+
const appModulePath = path.join(srcPath, 'app.module.ts');
|
|
729
|
+
let appModuleContent = fs.readFileSync(appModulePath, 'utf-8');
|
|
730
|
+
|
|
731
|
+
// Add StudentHelpModule import if not already present
|
|
732
|
+
if (!appModuleContent.includes('StudentHelpModule')) {
|
|
733
|
+
// Add import statement at the beginning
|
|
734
|
+
appModuleContent = appModuleContent.replace(
|
|
735
|
+
"import { Module } from '@nestjs/common';",
|
|
736
|
+
"import { Module } from '@nestjs/common';\nimport { StudentHelpModule } from './student-help/student-help.module';"
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
// Add to imports array
|
|
740
|
+
appModuleContent = appModuleContent.replace(
|
|
741
|
+
'imports: [',
|
|
742
|
+
'imports: [StudentHelpModule,'
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
fs.writeFileSync(appModulePath, appModuleContent);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Install bcrypt if not already installed
|
|
749
|
+
try {
|
|
750
|
+
execSync('npm list bcrypt 2>&1 | grep bcrypt', { stdio: 'ignore', cwd: backendPath });
|
|
751
|
+
} catch {
|
|
752
|
+
execSync('npm install bcrypt 2>&1 > /dev/null', { stdio: 'ignore', cwd: backendPath });
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
spinner.succeed(chalk.green('StudentHelp module setup complete'));
|
|
756
|
+
} catch (error) {
|
|
757
|
+
spinner.fail(chalk.red('Error setting up StudentHelp module'));
|
|
758
|
+
console.error(error.message);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Success banner
|
|
762
|
+
console.log(chalk.hex('#00FF00').bold('\n' + '═'.repeat(60)));
|
|
763
|
+
console.log(chalk.hex('#00FF00').bold('║') + chalk.hex('#39FF14').bold(' ✨ Project Created Successfully Fasttt! ✨').padEnd(58) + chalk.hex('#00FF00').bold('║'));
|
|
764
|
+
console.log(chalk.hex('#00FF00').bold('═'.repeat(60)));
|
|
765
|
+
|
|
766
|
+
console.log(chalk.cyan.bold('\n📂 Next Steps:\n'));
|
|
767
|
+
console.log(chalk.white(` ${chalk.hex('#00D9FF')('$')} cd ${projectName}`));
|
|
768
|
+
console.log(chalk.white(` ${chalk.hex('#00D9FF')('$')} docker-compose up -d\n`));
|
|
769
|
+
|
|
770
|
+
console.log(chalk.hex('#FFD700').bold('🌐 Access Points:\n'));
|
|
771
|
+
console.log(chalk.white(` Frontend: ${chalk.hex('#FF00FF').bold('http://localhost:3001')}`));
|
|
772
|
+
console.log(chalk.white(` Backend: ${chalk.hex('#00FFFF').bold('http://localhost:3000')}`));
|
|
773
|
+
console.log(chalk.white(` Database: ${chalk.hex('#FFD700').bold('localhost:5432')}\n`));
|
|
774
|
+
|
|
775
|
+
console.log(chalk.hex('#39FF14').bold('💡 Useful Commands:\n'));
|
|
776
|
+
console.log(chalk.white(` student-help generate:auth`));
|
|
777
|
+
console.log(chalk.white(` student-help generate:resource User\n`));
|
|
778
|
+
|
|
779
|
+
console.log(chalk.hex('#00FF00').bold('═'.repeat(60) + '\n'));
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
// ============================================
|
|
783
|
+
// Command: dev
|
|
784
|
+
// ============================================
|
|
785
|
+
const devCommand = async () => {
|
|
786
|
+
printBanner();
|
|
787
|
+
|
|
788
|
+
const spinner = ora({
|
|
789
|
+
text: 'Starting Docker containers',
|
|
790
|
+
spinner: 'dots3',
|
|
791
|
+
color: 'cyan'
|
|
792
|
+
}).start();
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
execSync('docker-compose up -d', { stdio: 'ignore' });
|
|
796
|
+
spinner.succeed(chalk.green('Docker containers running'));
|
|
797
|
+
|
|
798
|
+
console.log(chalk.blue.bold('\n⏳ Waiting for services to initialize...\n'));
|
|
799
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
800
|
+
|
|
801
|
+
console.log(chalk.green.bold('✓ Development Environment Ready!\n'));
|
|
802
|
+
console.log(chalk.cyan.bold('Access:\n'));
|
|
803
|
+
console.log(chalk.white(` Frontend: ${chalk.yellow.bold('http://localhost:3001')}`));
|
|
804
|
+
console.log(chalk.white(` Backend: ${chalk.yellow.bold('http://localhost:3000')}`));
|
|
805
|
+
console.log(chalk.white(` Database: ${chalk.yellow.bold('localhost:5432')}\n`));
|
|
806
|
+
|
|
807
|
+
console.log(chalk.yellow('To stop: docker-compose down\n'));
|
|
808
|
+
} catch (error) {
|
|
809
|
+
spinner.fail(chalk.red('Error starting environment'));
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// ============================================
|
|
815
|
+
// Command: db <database>
|
|
816
|
+
// ============================================
|
|
817
|
+
const dbCommand = async (database) => {
|
|
818
|
+
console.log(chalk.blue(`\n📦 Database Information: ${database}\n`));
|
|
819
|
+
|
|
820
|
+
switch (database.toLowerCase()) {
|
|
821
|
+
case 'postgres':
|
|
822
|
+
case 'postgresql':
|
|
823
|
+
console.log(chalk.green('✅ PostgreSQL is included!\n'));
|
|
824
|
+
console.log(chalk.white('Credentials:'));
|
|
825
|
+
console.log(chalk.cyan(' Host: postgres (Docker) / localhost (local)'));
|
|
826
|
+
console.log(chalk.cyan(' User: student'));
|
|
827
|
+
console.log(chalk.cyan(' Password: student123\n'));
|
|
828
|
+
break;
|
|
829
|
+
|
|
830
|
+
case 'mongodb':
|
|
831
|
+
case 'mongo':
|
|
832
|
+
console.log(chalk.yellow('To add MongoDB, edit docker-compose.yml\n'));
|
|
833
|
+
break;
|
|
834
|
+
|
|
835
|
+
case 'redis':
|
|
836
|
+
console.log(chalk.yellow('To add Redis, edit docker-compose.yml\n'));
|
|
837
|
+
break;
|
|
838
|
+
|
|
839
|
+
default:
|
|
840
|
+
console.log(chalk.red(`Database '${database}' not recognized`));
|
|
841
|
+
console.log(chalk.cyan('Available: postgres, mongodb, redis\n'));
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// ============================================
|
|
846
|
+
// Command: generate:auth
|
|
847
|
+
// ============================================
|
|
848
|
+
const generateAuthCommand = async () => {
|
|
849
|
+
const spinner = ora({
|
|
850
|
+
text: 'Generating Auth module',
|
|
851
|
+
spinner: 'dots3',
|
|
852
|
+
color: 'magenta'
|
|
853
|
+
}).start();
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
execSync('cd backend && nest g module auth 2>&1 > /dev/null', { stdio: 'ignore' });
|
|
857
|
+
execSync('cd backend && nest g controller auth 2>&1 > /dev/null', { stdio: 'ignore' });
|
|
858
|
+
execSync('cd backend && nest g service auth 2>&1 > /dev/null', { stdio: 'ignore' });
|
|
859
|
+
|
|
860
|
+
spinner.succeed(chalk.green('Module created'));
|
|
861
|
+
|
|
862
|
+
const depsSpinner = ora({
|
|
863
|
+
text: 'Installing dependencies',
|
|
864
|
+
spinner: 'arc',
|
|
865
|
+
color: 'blue'
|
|
866
|
+
}).start();
|
|
867
|
+
|
|
868
|
+
execSync('cd backend && npm install @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt bcrypt 2>&1 > /dev/null', { stdio: 'ignore' });
|
|
869
|
+
execSync('cd backend && npm install -D @types/bcrypt 2>&1 > /dev/null', { stdio: 'ignore' });
|
|
870
|
+
|
|
871
|
+
depsSpinner.succeed(chalk.green('Dependencies installed'));
|
|
872
|
+
|
|
873
|
+
console.log(chalk.green.bold('\n✓ Auth Module Created!\n'));
|
|
874
|
+
console.log(chalk.yellow('Next steps:\n'));
|
|
875
|
+
console.log(chalk.white(' 1. Configure JWT_SECRET in .env'));
|
|
876
|
+
console.log(chalk.white(' 2. Implement logic in backend/src/auth/'));
|
|
877
|
+
console.log(chalk.white(' 3. Register module in app.module.ts\n'));
|
|
878
|
+
} catch (error) {
|
|
879
|
+
spinner.fail(chalk.red('Error generating auth'));
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// ============================================
|
|
885
|
+
// Command: generate:resource <name>
|
|
886
|
+
// ============================================
|
|
887
|
+
const generateResourceCommand = async (resourceName) => {
|
|
888
|
+
const resourceLower = resourceName.toLowerCase();
|
|
889
|
+
const resourcePascal = resourceName.charAt(0).toUpperCase() + resourceName.slice(1);
|
|
890
|
+
|
|
891
|
+
const spinner = ora({
|
|
892
|
+
text: `Generating ${resourcePascal} CRUD`,
|
|
893
|
+
spinner: 'bouncingBar',
|
|
894
|
+
color: 'yellow'
|
|
895
|
+
}).start();
|
|
896
|
+
|
|
897
|
+
try {
|
|
898
|
+
const backendPath = path.join(process.cwd(), 'backend/src', resourceLower);
|
|
899
|
+
fs.ensureDirSync(backendPath);
|
|
900
|
+
|
|
901
|
+
// Module
|
|
902
|
+
const moduleContent = [
|
|
903
|
+
'import { Module } from \'@nestjs/common\';',
|
|
904
|
+
`import { ${resourcePascal}Service } from './${resourceLower}.service';`,
|
|
905
|
+
`import { ${resourcePascal}Controller } from './${resourceLower}.controller';`,
|
|
906
|
+
'',
|
|
907
|
+
`@Module({`,
|
|
908
|
+
` controllers: [${resourcePascal}Controller],`,
|
|
909
|
+
` providers: [${resourcePascal}Service],`,
|
|
910
|
+
`})`,
|
|
911
|
+
`export class ${resourcePascal}Module {}`,
|
|
912
|
+
].join('\n');
|
|
913
|
+
|
|
914
|
+
fs.writeFileSync(path.join(backendPath, `${resourceLower}.module.ts`), moduleContent);
|
|
915
|
+
|
|
916
|
+
// Service
|
|
917
|
+
const serviceContent = [
|
|
918
|
+
'import { Injectable } from \'@nestjs/common\';',
|
|
919
|
+
`import { Create${resourcePascal}Dto } from './dto/create-${resourceLower}.dto';`,
|
|
920
|
+
`import { Update${resourcePascal}Dto } from './dto/update-${resourceLower}.dto';`,
|
|
921
|
+
'',
|
|
922
|
+
'@Injectable()',
|
|
923
|
+
`export class ${resourcePascal}Service {`,
|
|
924
|
+
` create(create${resourcePascal}Dto: Create${resourcePascal}Dto) {`,
|
|
925
|
+
` return 'Create endpoint';`,
|
|
926
|
+
` }`,
|
|
927
|
+
` findAll() {`,
|
|
928
|
+
` return 'Find all endpoint';`,
|
|
929
|
+
` }`,
|
|
930
|
+
` findOne(id: number) {`,
|
|
931
|
+
` return { id };`,
|
|
932
|
+
` }`,
|
|
933
|
+
` update(id: number, update${resourcePascal}Dto: Update${resourcePascal}Dto) {`,
|
|
934
|
+
` return { id, data: update${resourcePascal}Dto };`,
|
|
935
|
+
` }`,
|
|
936
|
+
` remove(id: number) {`,
|
|
937
|
+
` return { id };`,
|
|
938
|
+
` }`,
|
|
939
|
+
`}`,
|
|
940
|
+
].join('\n');
|
|
941
|
+
|
|
942
|
+
fs.writeFileSync(path.join(backendPath, `${resourceLower}.service.ts`), serviceContent);
|
|
943
|
+
|
|
944
|
+
// Controller
|
|
945
|
+
const controllerContent = [
|
|
946
|
+
'import {',
|
|
947
|
+
' Controller,',
|
|
948
|
+
' Get,',
|
|
949
|
+
' Post,',
|
|
950
|
+
' Body,',
|
|
951
|
+
' Patch,',
|
|
952
|
+
' Param,',
|
|
953
|
+
' Delete,',
|
|
954
|
+
'} from \'@nestjs/common\';',
|
|
955
|
+
`import { ${resourcePascal}Service } from './${resourceLower}.service';`,
|
|
956
|
+
`import { Create${resourcePascal}Dto } from './dto/create-${resourceLower}.dto';`,
|
|
957
|
+
`import { Update${resourcePascal}Dto } from './dto/update-${resourceLower}.dto';`,
|
|
958
|
+
'',
|
|
959
|
+
`@Controller('${resourceLower}')`,
|
|
960
|
+
`export class ${resourcePascal}Controller {`,
|
|
961
|
+
` constructor(private readonly ${resourceLower}Service: ${resourcePascal}Service) {}`,
|
|
962
|
+
'',
|
|
963
|
+
` @Post()`,
|
|
964
|
+
` create(@Body() create${resourcePascal}Dto: Create${resourcePascal}Dto) {`,
|
|
965
|
+
` return this.${resourceLower}Service.create(create${resourcePascal}Dto);`,
|
|
966
|
+
` }`,
|
|
967
|
+
` @Get()`,
|
|
968
|
+
` findAll() {`,
|
|
969
|
+
` return this.${resourceLower}Service.findAll();`,
|
|
970
|
+
` }`,
|
|
971
|
+
` @Get(':id')`,
|
|
972
|
+
` findOne(@Param('id') id: string) {`,
|
|
973
|
+
` return this.${resourceLower}Service.findOne(+id);`,
|
|
974
|
+
` }`,
|
|
975
|
+
` @Patch(':id')`,
|
|
976
|
+
` update(`,
|
|
977
|
+
` @Param('id') id: string,`,
|
|
978
|
+
` @Body() update${resourcePascal}Dto: Update${resourcePascal}Dto,`,
|
|
979
|
+
` ) {`,
|
|
980
|
+
` return this.${resourceLower}Service.update(+id, update${resourcePascal}Dto);`,
|
|
981
|
+
` }`,
|
|
982
|
+
` @Delete(':id')`,
|
|
983
|
+
` remove(@Param('id') id: string) {`,
|
|
984
|
+
` return this.${resourceLower}Service.remove(+id);`,
|
|
985
|
+
` }`,
|
|
986
|
+
`}`,
|
|
987
|
+
].join('\n');
|
|
988
|
+
|
|
989
|
+
fs.writeFileSync(path.join(backendPath, `${resourceLower}.controller.ts`), controllerContent);
|
|
990
|
+
|
|
991
|
+
// DTOs
|
|
992
|
+
const dtoDir = path.join(backendPath, 'dto');
|
|
993
|
+
fs.ensureDirSync(dtoDir);
|
|
994
|
+
|
|
995
|
+
const createDtoContent = `export class Create${resourcePascal}Dto {
|
|
996
|
+
name: string;
|
|
997
|
+
description?: string;
|
|
998
|
+
email?: string;
|
|
999
|
+
}`;
|
|
1000
|
+
|
|
1001
|
+
const updateDtoContent = `export class Update${resourcePascal}Dto {
|
|
1002
|
+
name?: string;
|
|
1003
|
+
description?: string;
|
|
1004
|
+
email?: string;
|
|
1005
|
+
}`;
|
|
1006
|
+
|
|
1007
|
+
fs.writeFileSync(path.join(dtoDir, `create-${resourceLower}.dto.ts`), createDtoContent);
|
|
1008
|
+
fs.writeFileSync(path.join(dtoDir, `update-${resourceLower}.dto.ts`), updateDtoContent);
|
|
1009
|
+
|
|
1010
|
+
// Frontend
|
|
1011
|
+
const frontendPath = path.join(process.cwd(), 'frontend/app', resourceLower);
|
|
1012
|
+
fs.ensureDirSync(frontendPath);
|
|
1013
|
+
|
|
1014
|
+
const pageContent = `'use client';
|
|
1015
|
+
|
|
1016
|
+
import React, { useState } from 'react';
|
|
1017
|
+
|
|
1018
|
+
interface ${resourcePascal} {
|
|
1019
|
+
id?: number;
|
|
1020
|
+
name: string;
|
|
1021
|
+
description?: string;
|
|
1022
|
+
email?: string;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export default function ${resourcePascal}Page() {
|
|
1026
|
+
const [items, setItems] = useState<${resourcePascal}[]>([]);
|
|
1027
|
+
const [form, setForm] = useState<${resourcePascal}>({
|
|
1028
|
+
name: '',
|
|
1029
|
+
description: '',
|
|
1030
|
+
email: '',
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
1034
|
+
e.preventDefault();
|
|
1035
|
+
if (form.name) {
|
|
1036
|
+
setItems([...items, { ...form, id: Date.now() }]);
|
|
1037
|
+
setForm({ name: '', description: '', email: '' });
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
return (
|
|
1042
|
+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4">
|
|
1043
|
+
<div className="max-w-4xl mx-auto">
|
|
1044
|
+
<div className="bg-white rounded-lg shadow-xl p-8">
|
|
1045
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-2">${resourcePascal} Management</h1>
|
|
1046
|
+
<p className="text-gray-600 mb-8">Create and manage ${resourceLower} records</p>
|
|
1047
|
+
|
|
1048
|
+
<form onSubmit={handleSubmit} className="mb-8">
|
|
1049
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
1050
|
+
<input
|
|
1051
|
+
type="text"
|
|
1052
|
+
placeholder="Name"
|
|
1053
|
+
value={form.name}
|
|
1054
|
+
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
1055
|
+
className="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1056
|
+
/>
|
|
1057
|
+
<input
|
|
1058
|
+
type="email"
|
|
1059
|
+
placeholder="Email"
|
|
1060
|
+
value={form.email || ''}
|
|
1061
|
+
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
|
1062
|
+
className="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1063
|
+
/>
|
|
1064
|
+
<input
|
|
1065
|
+
type="text"
|
|
1066
|
+
placeholder="Description"
|
|
1067
|
+
value={form.description || ''}
|
|
1068
|
+
onChange={(e) => setForm({ ...form, description: e.target.value })}
|
|
1069
|
+
className="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1070
|
+
/>
|
|
1071
|
+
</div>
|
|
1072
|
+
<button
|
|
1073
|
+
type="submit"
|
|
1074
|
+
className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition"
|
|
1075
|
+
>
|
|
1076
|
+
Add ${resourcePascal}
|
|
1077
|
+
</button>
|
|
1078
|
+
</form>
|
|
1079
|
+
|
|
1080
|
+
<div className="grid gap-4">
|
|
1081
|
+
{items.length === 0 ? (
|
|
1082
|
+
<p className="text-center text-gray-400 py-8">No ${resourceLower}s created yet</p>
|
|
1083
|
+
) : (
|
|
1084
|
+
items.map((item) => (
|
|
1085
|
+
<div key={item.id} className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition">
|
|
1086
|
+
<h3 className="font-semibold text-lg text-gray-900">{item.name}</h3>
|
|
1087
|
+
{item.email && <p className="text-sm text-gray-600">{item.email}</p>}
|
|
1088
|
+
{item.description && <p className="text-sm text-gray-500">{item.description}</p>}
|
|
1089
|
+
</div>
|
|
1090
|
+
))
|
|
1091
|
+
)}
|
|
1092
|
+
</div>
|
|
1093
|
+
</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
</div>
|
|
1096
|
+
);
|
|
1097
|
+
}`;
|
|
1098
|
+
|
|
1099
|
+
fs.writeFileSync(path.join(frontendPath, 'page.tsx'), pageContent);
|
|
1100
|
+
|
|
1101
|
+
spinner.succeed(chalk.green(`${resourcePascal} resource generated`));
|
|
1102
|
+
|
|
1103
|
+
console.log(chalk.green.bold(`\n✓ ${resourcePascal} Created!\n`));
|
|
1104
|
+
console.log(chalk.yellow('Next steps:\n'));
|
|
1105
|
+
console.log(chalk.white(` 1. Register module in backend/src/app.module.ts`));
|
|
1106
|
+
console.log(chalk.white(` 2. Implement logic in backend/src/${resourceLower}/`));
|
|
1107
|
+
console.log(chalk.white(` 3. Customize component in frontend/app/${resourceLower}/page.tsx\n`));
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
spinner.fail(chalk.red('Error generating resource'));
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
// ============================================
|
|
1115
|
+
// CLI Setup
|
|
1116
|
+
// ============================================
|
|
1117
|
+
const program = new Command();
|
|
1118
|
+
|
|
1119
|
+
program
|
|
1120
|
+
.version('2.0.0')
|
|
1121
|
+
.description(chalk.hex('#FF00FF').bold('✨ Student Help Framework') + ' - Create full-stack apps in minutes');
|
|
1122
|
+
|
|
1123
|
+
program
|
|
1124
|
+
.command('new <projectName>')
|
|
1125
|
+
.description('✨ Create a new full-stack project with NestJS + Next.js + PostgreSQL')
|
|
1126
|
+
.action(async (projectName) => {
|
|
1127
|
+
try {
|
|
1128
|
+
await newCommand(projectName);
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
program
|
|
1136
|
+
.command('dev')
|
|
1137
|
+
.description('🚀 Start development environment with Docker')
|
|
1138
|
+
.action(async () => {
|
|
1139
|
+
try {
|
|
1140
|
+
await devCommand();
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1143
|
+
process.exit(1);
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
program
|
|
1148
|
+
.command('db <database>')
|
|
1149
|
+
.description('📦 Database information and setup guide')
|
|
1150
|
+
.action(async (database) => {
|
|
1151
|
+
try {
|
|
1152
|
+
await dbCommand(database);
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
program
|
|
1160
|
+
.command('generate:auth')
|
|
1161
|
+
.description('🔐 Generate authentication module with JWT support')
|
|
1162
|
+
.action(async () => {
|
|
1163
|
+
try {
|
|
1164
|
+
await generateAuthCommand();
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1167
|
+
process.exit(1);
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
program
|
|
1172
|
+
.command('generate:resource <resourceName>')
|
|
1173
|
+
.description('🛠️ Generate CRUD resource (backend + frontend)')
|
|
1174
|
+
.action(async (resourceName) => {
|
|
1175
|
+
try {
|
|
1176
|
+
await generateResourceCommand(resourceName);
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1179
|
+
process.exit(1);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// ============================================
|
|
1184
|
+
// Command: commands
|
|
1185
|
+
// ============================================
|
|
1186
|
+
const commandsCommand = () => {
|
|
1187
|
+
printBanner();
|
|
1188
|
+
|
|
1189
|
+
console.log(chalk.hex('#FFD700').bold('\n📚 AVAILABLE COMMANDS\n'));
|
|
1190
|
+
console.log(chalk.hex('#00D9FF').bold('═'.repeat(60) + '\n'));
|
|
1191
|
+
|
|
1192
|
+
const commands = [
|
|
1193
|
+
{
|
|
1194
|
+
name: 'new <projectName>',
|
|
1195
|
+
emoji: '✨',
|
|
1196
|
+
description: 'Create a new full-stack project',
|
|
1197
|
+
details: 'Sets up NestJS backend, Next.js frontend, PostgreSQL, Docker Compose,\nand all modern dependencies in one command.',
|
|
1198
|
+
example: 'student-help new my-awesome-app'
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
name: 'dev',
|
|
1202
|
+
emoji: '🚀',
|
|
1203
|
+
description: 'Start development environment',
|
|
1204
|
+
details: 'Launches Docker Compose with all services:\nBackend (port 3000), Frontend (port 3001), Database (port 5432)',
|
|
1205
|
+
example: 'student-help dev'
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
name: 'db <database>',
|
|
1209
|
+
emoji: '📦',
|
|
1210
|
+
description: 'Database information and setup',
|
|
1211
|
+
details: 'Shows credentials and setup instructions for supported databases.\nSupported: postgres, mongodb, redis',
|
|
1212
|
+
example: 'student-help db postgres'
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
name: 'generate:auth',
|
|
1216
|
+
emoji: '🔐',
|
|
1217
|
+
description: 'Generate authentication module',
|
|
1218
|
+
details: 'Creates Auth Module with JWT support, Passport.js integration,\nand bcrypt password hashing setup.',
|
|
1219
|
+
example: 'student-help generate:auth'
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
name: 'generate:resource <name>',
|
|
1223
|
+
emoji: '🛠️',
|
|
1224
|
+
description: 'Generate CRUD resource',
|
|
1225
|
+
details: 'Auto-generates complete CRUD operations for backend (NestJS) and\nfrontend (React component) with database DTOs.',
|
|
1226
|
+
example: 'student-help generate:resource Post'
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
name: 'commands',
|
|
1230
|
+
emoji: '📚',
|
|
1231
|
+
description: 'List all available commands',
|
|
1232
|
+
details: 'Shows this help menu with detailed information about each command.',
|
|
1233
|
+
example: 'student-help commands'
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: 'features',
|
|
1237
|
+
emoji: '⚡',
|
|
1238
|
+
description: 'Show advanced features',
|
|
1239
|
+
details: 'Displays how to use advanced features from the features/ directory\nincluding custom modules and utilities.',
|
|
1240
|
+
example: 'student-help features'
|
|
1241
|
+
}
|
|
1242
|
+
];
|
|
1243
|
+
|
|
1244
|
+
commands.forEach((cmd, index) => {
|
|
1245
|
+
console.log(chalk.hex('#FF00FF').bold(`${cmd.emoji} ${cmd.name.padEnd(30)} ${cmd.description}`));
|
|
1246
|
+
console.log(chalk.gray(` ${cmd.details}`));
|
|
1247
|
+
console.log(chalk.hex('#00D9FF')(` $ ${cmd.example}`));
|
|
1248
|
+
if (index < commands.length - 1) {
|
|
1249
|
+
console.log('');
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
console.log(chalk.hex('#00D9FF').bold('\n' + '═'.repeat(60)));
|
|
1254
|
+
console.log(chalk.hex('#39FF14').bold('\n💡 Quick Start:'));
|
|
1255
|
+
console.log(chalk.white(' 1. student-help new my-app'));
|
|
1256
|
+
console.log(chalk.white(' 2. cd my-app'));
|
|
1257
|
+
console.log(chalk.white(' 3. docker-compose up -d'));
|
|
1258
|
+
console.log(chalk.white(' 4. Visit http://localhost:3001'));
|
|
1259
|
+
console.log(chalk.hex('#00D9FF').bold('\n' + '═'.repeat(60) + '\n'));
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// ============================================
|
|
1263
|
+
// Command: features
|
|
1264
|
+
// ============================================
|
|
1265
|
+
const featuresCommand = () => {
|
|
1266
|
+
printBanner();
|
|
1267
|
+
|
|
1268
|
+
console.log(chalk.hex('#FFD700').bold('\n⚡ ADVANCED FEATURES & UTILITIES\n'));
|
|
1269
|
+
console.log(chalk.hex('#00D9FF').bold('═'.repeat(60) + '\n'));
|
|
1270
|
+
|
|
1271
|
+
const features = [
|
|
1272
|
+
{
|
|
1273
|
+
category: '📁 Features Directory',
|
|
1274
|
+
items: [
|
|
1275
|
+
{
|
|
1276
|
+
name: 'Custom Middleware',
|
|
1277
|
+
description: 'Add authentication, logging, and error handling middleware'
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
name: 'Database Utilities',
|
|
1281
|
+
description: 'Pre-configured database connection pools and migrations'
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: 'API Response Formatters',
|
|
1285
|
+
description: 'Standardized response formats for your API'
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
name: 'Error Handlers',
|
|
1289
|
+
description: 'Global error handling with custom error messages'
|
|
1290
|
+
}
|
|
1291
|
+
]
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
category: '🔧 Configuration Options',
|
|
1295
|
+
items: [
|
|
1296
|
+
{
|
|
1297
|
+
name: 'Environment Variables',
|
|
1298
|
+
description: 'Use .env.example as template for your configuration'
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: 'Docker Compose Override',
|
|
1302
|
+
description: 'Create docker-compose.override.yml for local development changes'
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
name: 'Database Migrations',
|
|
1306
|
+
description: 'Use TypeORM CLI: npm run migration:generate'
|
|
1307
|
+
}
|
|
1308
|
+
]
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
category: '📚 Additional Commands',
|
|
1312
|
+
items: [
|
|
1313
|
+
{
|
|
1314
|
+
name: 'Backend Scripts',
|
|
1315
|
+
description: 'npm run start:dev (watch mode)\n npm run build (production build)\n npm run test (run tests)'
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: 'Frontend Scripts',
|
|
1319
|
+
description: 'npm run dev (dev server)\n npm run build (production build)\n npm run lint (check code quality)'
|
|
1320
|
+
}
|
|
1321
|
+
]
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
category: '🔐 Security Features',
|
|
1325
|
+
items: [
|
|
1326
|
+
{
|
|
1327
|
+
name: 'JWT Authentication',
|
|
1328
|
+
description: 'Pre-configured with Passport.js and bcrypt'
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
name: 'CORS Configuration',
|
|
1332
|
+
description: 'Cross-Origin Resource Sharing setup in backend'
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
name: 'Environment Security',
|
|
1336
|
+
description: 'Sensitive data protected via .env files'
|
|
1337
|
+
}
|
|
1338
|
+
]
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
category: '🚀 Performance Features',
|
|
1342
|
+
items: [
|
|
1343
|
+
{
|
|
1344
|
+
name: 'Code Splitting',
|
|
1345
|
+
description: 'Next.js automatic code splitting on frontend'
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
name: 'Caching',
|
|
1349
|
+
description: 'Built-in caching strategies with Redis support'
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
name: 'Database Indexing',
|
|
1353
|
+
description: 'Recommendations for optimal query performance'
|
|
1354
|
+
}
|
|
1355
|
+
]
|
|
1356
|
+
}
|
|
1357
|
+
];
|
|
1358
|
+
|
|
1359
|
+
features.forEach((section) => {
|
|
1360
|
+
console.log(chalk.hex('#FF00FF').bold(section.category));
|
|
1361
|
+
console.log('');
|
|
1362
|
+
|
|
1363
|
+
section.items.forEach((item) => {
|
|
1364
|
+
console.log(chalk.hex('#00D9FF')(` ✓ ${item.name}`));
|
|
1365
|
+
item.description.split('\n').forEach(line => {
|
|
1366
|
+
console.log(chalk.gray(` ${line}`));
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
console.log('');
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
console.log(chalk.hex('#00D9FF').bold('═'.repeat(60)));
|
|
1374
|
+
console.log(chalk.hex('#39FF14').bold('\n📖 Documentation:'));
|
|
1375
|
+
console.log(chalk.white(' - Backend: /backend/README.md'));
|
|
1376
|
+
console.log(chalk.white(' - Frontend: /frontend/README.md'));
|
|
1377
|
+
console.log(chalk.white(' - Project: README.md'));
|
|
1378
|
+
|
|
1379
|
+
console.log(chalk.hex('#FFD700').bold('\n🔗 Useful Links:'));
|
|
1380
|
+
console.log(chalk.white(' - NestJS: https://docs.nestjs.com'));
|
|
1381
|
+
console.log(chalk.white(' - Next.js: https://nextjs.org/docs'));
|
|
1382
|
+
console.log(chalk.white(' - PostgreSQL: https://www.postgresql.org/docs'));
|
|
1383
|
+
console.log(chalk.white(' - Tailwind CSS: https://tailwindcss.com/docs'));
|
|
1384
|
+
console.log(chalk.white(' - Docker: https://docs.docker.com'));
|
|
1385
|
+
console.log(chalk.hex('#00D9FF').bold('\n' + '═'.repeat(60) + '\n'));
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// ============================================
|
|
1389
|
+
// Register commands
|
|
1390
|
+
// ============================================
|
|
1391
|
+
program
|
|
1392
|
+
.command('commands')
|
|
1393
|
+
.description('📚 List all available commands with descriptions')
|
|
1394
|
+
.action(() => {
|
|
1395
|
+
try {
|
|
1396
|
+
commandsCommand();
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
program
|
|
1404
|
+
.command('features')
|
|
1405
|
+
.description('⚡ Show advanced features and utilities available')
|
|
1406
|
+
.action(() => {
|
|
1407
|
+
try {
|
|
1408
|
+
featuresCommand();
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
program.parse(process.argv);
|