npms-exam-kit 2.0.1 → 3.0.1
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/bin/exam-kit.js +482 -625
- package/package.json +1 -1
package/bin/exam-kit.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, join } from 'path';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import inquirer from 'inquirer';
|
|
7
|
-
import {
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
10
10
|
|
|
@@ -16,527 +16,484 @@ const execAsync = promisify(exec);
|
|
|
16
16
|
|
|
17
17
|
function read(p) { try { return existsSync(p) ? readFileSync(p, 'utf8') : ''; } catch { return ''; } }
|
|
18
18
|
function inDir(p) { try { return existsSync(p) ? readdirSync(p) : []; } catch { return []; } }
|
|
19
|
+
function log(m) { console.log(m); }
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
21
|
+
const SIMS_PAGES = ['Login.jsx','SparePart.jsx','StockIn.jsx','StockOut.jsx','Reports.jsx'];
|
|
22
|
+
const CRPMS_PAGES = ['Login.jsx','Cars.jsx','Services.jsx','ServiceRecords.jsx','Payments.jsx','Reports.jsx','Bill.jsx'];
|
|
23
|
+
|
|
24
|
+
const SIMS_ENDPOINTS = [
|
|
25
|
+
{ method:'POST', path:'/api/auth/login', body:'{"username":"admin","password":"admin123"}', desc:'Login' },
|
|
26
|
+
{ method:'POST', path:'/api/auth/logout', body:'{}', desc:'Logout' },
|
|
27
|
+
{ method:'GET', path:'/api/auth/check', body:'', desc:'Check auth session' },
|
|
28
|
+
{ method:'GET', path:'/api/spare-parts', body:'', desc:'List spare parts' },
|
|
29
|
+
{ method:'POST', path:'/api/spare-parts', body:'{"Name":"Brake Pad","Category":"Brakes","Quantity":50,"UnitPrice":15000}', desc:'Add spare part' },
|
|
30
|
+
{ method:'GET', path:'/api/stock-in', body:'', desc:'List stock in records' },
|
|
31
|
+
{ method:'POST', path:'/api/stock-in', body:'{"SparePartID":1,"StockInQuantity":20,"StockInDate":"2025-05-31"}', desc:'Record stock in' },
|
|
32
|
+
{ method:'GET', path:'/api/stock-out', body:'', desc:'List stock out records' },
|
|
33
|
+
{ method:'POST', path:'/api/stock-out', body:'{"SparePartID":1,"StockOutQuantity":5,"StockOutUnitPrice":18000,"StockOutDate":"2025-05-31"}', desc:'Record stock out' },
|
|
34
|
+
{ method:'PUT', path:'/api/stock-out/1', body:'{"SparePartID":1,"StockOutQuantity":3,"StockOutUnitPrice":18000,"StockOutDate":"2025-05-31"}', desc:'Update stock out' },
|
|
35
|
+
{ method:'DELETE', path:'/api/stock-out/1', body:'', desc:'Delete stock out' },
|
|
36
|
+
{ method:'GET', path:'/api/reports/daily-stockout?date=2025-05-31', body:'', desc:'Daily stock out report' },
|
|
37
|
+
{ method:'GET', path:'/api/reports/stock-status?date=2025-05-31', body:'', desc:'Stock status report' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const CRPMS_ENDPOINTS = [
|
|
41
|
+
{ method:'POST', path:'/api/auth/login', body:'{"username":"admin","password":"Admin@1234"}', desc:'Login' },
|
|
42
|
+
{ method:'POST', path:'/api/auth/logout', body:'{}', desc:'Logout' },
|
|
43
|
+
{ method:'GET', path:'/api/auth/me', body:'', desc:'Check auth session' },
|
|
44
|
+
{ method:'GET', path:'/api/cars', body:'', desc:'List cars' },
|
|
45
|
+
{ method:'POST', path:'/api/cars', body:'{"PlateNumber":"RAB123A","type":"Sedan","Model":"Toyota","ManufacturingYear":2020,"DriverPhone":"0788123456","MechanicName":"John"}', desc:'Add car' },
|
|
46
|
+
{ method:'GET', path:'/api/services', body:'', desc:'List services' },
|
|
47
|
+
{ method:'POST', path:'/api/services', body:'{"ServiceCode":"SRV007","ServiceName":"AC Repair","ServicePrice":75000}', desc:'Add service' },
|
|
48
|
+
{ method:'GET', path:'/api/service-records', body:'', desc:'List service records' },
|
|
49
|
+
{ method:'POST', path:'/api/service-records', body:'{"ServiceDate":"2025-05-31","PlateNumber":"RAB123A","ServiceCode":"SRV001"}', desc:'Create service record' },
|
|
50
|
+
{ method:'GET', path:'/api/service-records/1', body:'', desc:'Get one service record' },
|
|
51
|
+
{ method:'PUT', path:'/api/service-records/1', body:'{"ServiceDate":"2025-05-31","PlateNumber":"RAB123A","ServiceCode":"SRV002"}', desc:'Update service record' },
|
|
52
|
+
{ method:'DELETE', path:'/api/service-records/1', body:'', desc:'Delete service record' },
|
|
53
|
+
{ method:'GET', path:'/api/payments', body:'', desc:'List payments' },
|
|
54
|
+
{ method:'POST', path:'/api/payments', body:'{"AmountPaid":150000,"PaymentDate":"2025-05-31","RecordNumber":1}', desc:'Record payment' },
|
|
55
|
+
{ method:'GET', path:'/api/payments/1/bill', body:'', desc:'Get payment bill' },
|
|
56
|
+
{ method:'GET', path:'/api/reports/daily?date=2025-05-31', body:'', desc:'Daily report' },
|
|
57
|
+
{ method:'GET', path:'/api/reports/summary', body:'', desc:'Summary report' },
|
|
58
|
+
];
|
|
49
59
|
|
|
50
60
|
function buildChecklist(baseDir, projectType) {
|
|
51
61
|
const b = p => join(baseDir, p);
|
|
52
62
|
const fe = p => join(baseDir, 'frontend-project', p);
|
|
53
63
|
const be = p => join(baseDir, 'backend-project', p);
|
|
54
64
|
const src = p => join(baseDir, 'frontend-project', 'src', p);
|
|
55
|
-
const pages = projectType === 'SIMS' ? SIMS_PAGES : CRPMS_PAGES;
|
|
56
65
|
const dbName = projectType === 'SIMS' ? 'SIMS' : 'CRPMS';
|
|
57
|
-
|
|
58
66
|
const checks = [
|
|
59
|
-
{ id:
|
|
60
|
-
auto:
|
|
61
|
-
{ id:
|
|
62
|
-
auto:
|
|
63
|
-
{ id:
|
|
64
|
-
auto:
|
|
65
|
-
{ id:
|
|
66
|
-
auto:
|
|
67
|
-
{ id:
|
|
68
|
-
auto:
|
|
69
|
-
{ id:
|
|
70
|
-
auto:
|
|
71
|
-
{ id:
|
|
72
|
-
auto:
|
|
73
|
-
{ id:
|
|
74
|
-
auto:
|
|
75
|
-
{ id:
|
|
76
|
-
auto:
|
|
77
|
-
{ id:
|
|
78
|
-
auto:
|
|
79
|
-
{ id:
|
|
80
|
-
auto:
|
|
81
|
-
{ id:
|
|
82
|
-
auto:
|
|
83
|
-
{ id:
|
|
84
|
-
auto:
|
|
85
|
-
{ id:
|
|
86
|
-
auto:
|
|
87
|
-
{ id:
|
|
88
|
-
auto:
|
|
89
|
-
{ id:
|
|
90
|
-
auto:
|
|
91
|
-
{ id:
|
|
92
|
-
auto:
|
|
93
|
-
{ id:
|
|
94
|
-
auto:
|
|
95
|
-
{ id:
|
|
96
|
-
auto:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
guide:
|
|
104
|
-
{ id:
|
|
105
|
-
auto:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
guide:
|
|
110
|
-
{ id:
|
|
111
|
-
auto:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
guide:
|
|
116
|
-
{ id:
|
|
117
|
-
auto:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{ id:
|
|
135
|
-
auto:
|
|
136
|
-
guide:
|
|
137
|
-
{ id:
|
|
138
|
-
auto:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{ id:
|
|
150
|
-
auto:
|
|
151
|
-
guide: '
|
|
152
|
-
{ id:
|
|
153
|
-
auto:
|
|
154
|
-
guide: '
|
|
155
|
-
{ id:
|
|
156
|
-
auto:
|
|
157
|
-
guide: '
|
|
158
|
-
{ id:
|
|
159
|
-
auto:
|
|
160
|
-
guide:
|
|
161
|
-
{ id:
|
|
162
|
-
auto:
|
|
163
|
-
guide:
|
|
164
|
-
{ id:
|
|
165
|
-
auto:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{ id:
|
|
174
|
-
auto:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
guide:
|
|
179
|
-
{ id:
|
|
180
|
-
auto:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
guide:
|
|
185
|
-
{ id:
|
|
186
|
-
auto:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{ id:
|
|
192
|
-
auto:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{ id:
|
|
198
|
-
auto:
|
|
199
|
-
guide:
|
|
200
|
-
{ id:
|
|
201
|
-
auto:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
guide:
|
|
206
|
-
{ id:
|
|
207
|
-
auto:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
guide:
|
|
245
|
-
{ id:
|
|
246
|
-
auto:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
auto: () => 'MANUAL', guide: 'Prepare a brief presentation covering: product name, key steps, importance, challenges, solutions' },
|
|
250
|
-
{ id: 'C71', cat: 'Product (30%)', label: 'Input validation prevents invalid data',
|
|
251
|
-
auto: () => { const r = read(be('routes/stockOut.js')) || read(be('routes/payments.js')); return r.includes('return res.status(400)') ? 'PASS' : 'FAIL';
|
|
252
|
-
}, guide: 'Add validation checks: if (!field) return res.status(400).json({error: "Field required"})' },
|
|
253
|
-
{ id: 'C72', cat: 'Product (30%)', label: 'Form submission saves data to DB',
|
|
254
|
-
auto: () => { const r = inDir(be('routes')); return r.some(f => read(be('routes/'+f)).includes('INSERT INTO') || read(be('routes/'+f)).includes('res.status(201)')) ? 'PASS' : 'FAIL'; },
|
|
255
|
-
guide: 'Use INSERT INTO SQL statements in POST route handlers' },
|
|
256
|
-
{ id: 'C73', cat: 'Product (30%)', label: 'Data retrieval displays records',
|
|
257
|
-
auto: () => { const r = inDir(be('routes')); return r.some(f => read(be('routes/'+f)).includes('SELECT') && read(be('routes/'+f)).includes('router.get')) ? 'PASS' : 'FAIL'; },
|
|
258
|
-
guide: 'Use SELECT queries in GET route handlers and return JSON' },
|
|
259
|
-
{ id: 'C74', cat: 'Product (30%)', label: 'Update functionality works (PUT)',
|
|
260
|
-
auto: () => inDir(be('routes')).some(f => read(be('routes/'+f)).includes('UPDATE') && read(be('routes/'+f)).includes('router.put')) ? 'PASS' : 'FAIL',
|
|
261
|
-
guide: 'Use UPDATE SQL in PUT route handlers' },
|
|
262
|
-
{ id: 'C75', cat: 'Product (30%)', label: 'Delete functionality works (DELETE)',
|
|
263
|
-
auto: () => inDir(be('routes')).some(f => read(be('routes/'+f)).includes('DELETE') && read(be('routes/'+f)).includes('router.delete')) ? 'PASS' : 'FAIL',
|
|
264
|
-
guide: 'Use DELETE SQL in DELETE route handlers' },
|
|
265
|
-
{ id: 'C76', cat: 'Product (30%)', label: 'Navigation menus are interactive',
|
|
266
|
-
auto: () => read(src('components/Navbar.jsx')).includes('useState') && read(src('components/Navbar.jsx')).includes('onClick') ? 'PASS' : 'FAIL',
|
|
267
|
-
guide: 'Add onClick handlers and responsive toggle (hamburger menu) in Navbar' },
|
|
268
|
-
{ id: 'C77', cat: 'Product (30%)', label: 'Buttons are interactive',
|
|
269
|
-
auto: () => (read(src('pages/Login.jsx')).includes('onClick') || read(src('pages/Login.jsx')).includes('onSubmit')) ? 'PASS' : 'FAIL',
|
|
270
|
-
guide: 'Add onClick or onSubmit event handlers to buttons' },
|
|
271
|
-
{ id: 'C78', cat: 'Product (30%)', label: 'Responsive design (flexbox/grid)',
|
|
272
|
-
auto: () => (read(src('components/Navbar.jsx')).includes('md:') || read(src('App.jsx')).includes('flex') || read(src('index.css')).includes('grid')) ? 'PASS' : 'FAIL',
|
|
273
|
-
guide: 'Use Tailwind responsive prefixes (sm:, md:, lg:) and flexbox/grid' },
|
|
274
|
-
{ id: 'C79', cat: 'Product (30%)', label: `Daily ${projectType === 'SIMS' ? 'StockOut' : 'Service'} report generated`,
|
|
275
|
-
auto: () => read(be('routes/reports.js')).includes(projectType === 'SIMS' ? 'daily-stockout' : '/daily') ? 'PASS' : 'FAIL',
|
|
276
|
-
guide: `Add GET /api/reports/${projectType === 'SIMS' ? 'daily-stockout' : 'daily'} endpoint in routes/reports.js` },
|
|
277
|
-
{ id: 'C80', cat: 'Product (30%)', label: `Daily Stock Status report generated`,
|
|
278
|
-
auto: () => read(be('routes/reports.js')).includes('stock-status') || read(be('routes/reports.js')).includes('summary') ? 'PASS' : 'FAIL',
|
|
279
|
-
guide: `Add GET /api/reports/stock-status with Stored Quantity, StockOut, Remaining query` },
|
|
280
|
-
{ id: 'C81', cat: 'Product (30%)', label: 'Colors and styling are consistent',
|
|
281
|
-
auto: () => read(src('index.css')).length > 0 && read(fe('tailwind.config.js') || '').length > 0 ? 'PASS' : 'FAIL',
|
|
282
|
-
guide: 'Use a consistent color palette in Tailwind config (primary, secondary, accent)' },
|
|
283
|
-
{ id: 'C82', cat: 'Closing (5%)', label: 'Project folder can be removed after marking',
|
|
284
|
-
auto: () => 'MANUAL', guide: 'After assessor approval, delete the project folder' },
|
|
285
|
-
{ id: 'C83', cat: 'Closing (5%)', label: 'Database can be deleted after marking',
|
|
286
|
-
auto: () => 'MANUAL', guide: 'Run: DROP DATABASE ' + dbName + '; in MySQL' },
|
|
287
|
-
{ id: 'C84', cat: 'Closing (5%)', label: 'Global dependencies removed',
|
|
288
|
-
auto: () => 'MANUAL', guide: 'Uninstall global packages: npm uninstall -g nodemon etc.' },
|
|
67
|
+
{ id:'C1', cat:'Preliminary (15%)', label:'ERD Spare_Part entity drawn',
|
|
68
|
+
auto:()=>'MANUAL', guide:'Draw Spare_Part entity on paper with entity symbol' },
|
|
69
|
+
{ id:'C2', cat:'Preliminary (15%)', label:'ERD Stock_In entity drawn',
|
|
70
|
+
auto:()=>'MANUAL', guide:'Draw Stock_In entity on paper' },
|
|
71
|
+
{ id:'C3', cat:'Preliminary (15%)', label:'ERD Stock_Out entity drawn',
|
|
72
|
+
auto:()=>'MANUAL', guide:'Draw Stock_Out entity on paper' },
|
|
73
|
+
{ id:'C4', cat:'Preliminary (15%)', label:'ERD entity symbol used',
|
|
74
|
+
auto:()=>'MANUAL', guide:'Use rectangle/box symbol for each entity' },
|
|
75
|
+
{ id:'C5', cat:'Preliminary (15%)', label:'ERD relationship symbol used',
|
|
76
|
+
auto:()=>'MANUAL', guide:'Use diamond/line symbols for relationships' },
|
|
77
|
+
{ id:'C6', cat:'Preliminary (15%)', label:'ERD link symbol used',
|
|
78
|
+
auto:()=>'MANUAL', guide:'Use connecting lines between entities and relationships' },
|
|
79
|
+
{ id:'C7', cat:'Preliminary (15%)', label:'ERD Primary Key rule respected',
|
|
80
|
+
auto:()=>'MANUAL', guide:'Underline PK attributes in each entity' },
|
|
81
|
+
{ id:'C8', cat:'Preliminary (15%)', label:'ERD Foreign Key rule respected',
|
|
82
|
+
auto:()=>'MANUAL', guide:'Mark FK attributes with dotted underline' },
|
|
83
|
+
{ id:'C9', cat:'Preliminary (15%)', label:'ERD cardinalities indicated',
|
|
84
|
+
auto:()=>'MANUAL', guide:'Show 1---* or M---N on relationship lines' },
|
|
85
|
+
{ id:'C10', cat:'Preliminary (15%)', label:'Spare_Part-Stock_In relationship drawn',
|
|
86
|
+
auto:()=>'MANUAL', guide:'Connect Spare_Part (1) to Stock_In (*) via SparePartID FK' },
|
|
87
|
+
{ id:'C11', cat:'Preliminary (15%)', label:'Users-Stock_Out relationship drawn',
|
|
88
|
+
auto:()=>'MANUAL', guide:'Connect Users (1) to Stock_Out (*) via UserID FK' },
|
|
89
|
+
{ id:'C12', cat:'Preliminary (15%)', label:'Spare_Part-Stock_Out relationship drawn',
|
|
90
|
+
auto:()=>'MANUAL', guide:'Connect Spare_Part (1) to Stock_Out (*) via SparePartID FK' },
|
|
91
|
+
{ id:'C13', cat:'Preliminary (15%)', label:'Users PK indicated',
|
|
92
|
+
auto:()=>'MANUAL', guide:'Underline UserID in Users entity' },
|
|
93
|
+
{ id:'C14', cat:'Preliminary (15%)', label:'Spare_Part PK indicated',
|
|
94
|
+
auto:()=>'MANUAL', guide:'Underline SparePartID in Spare_Part' },
|
|
95
|
+
{ id:'C15', cat:'Preliminary (15%)', label:'Stock_In PK indicated',
|
|
96
|
+
auto:()=>'MANUAL', guide:'Underline StockInID in Stock_In' },
|
|
97
|
+
{ id:'C16', cat:'Preliminary (15%)', label:'Stock_Out PK indicated',
|
|
98
|
+
auto:()=>'MANUAL', guide:'Underline StockOutID in Stock_Out' },
|
|
99
|
+
{ id:'C17', cat:'Preliminary (15%)', label:'Users FK in Stock_Out indicated',
|
|
100
|
+
auto:()=>'MANUAL', guide:'Mark UserID as FK in Stock_Out' },
|
|
101
|
+
{ id:'C18', cat:'Preliminary (15%)', label:'Spare_Part FK in Stock_In indicated',
|
|
102
|
+
auto:()=>'MANUAL', guide:'Mark SparePartID as FK in Stock_In' },
|
|
103
|
+
{ id:'C19', cat:'Preliminary (15%)', label:'Spare_Part FK in Stock_Out indicated',
|
|
104
|
+
auto:()=>'MANUAL', guide:'Mark SparePartID as FK in Stock_Out' },
|
|
105
|
+
{ id:'C20', cat:'Process (50%)', label:'Project folder named FirstName_LastName_Exam_2025',
|
|
106
|
+
auto:()=>!baseDir.includes('node_modules')&&baseDir.split('_').length>=3?'PASS':'FAIL',
|
|
107
|
+
guide:'Rename parent folder to FirstName_LastName_National_Practical_Exam_2025' },
|
|
108
|
+
{ id:'C21', cat:'Process (50%)', label:'Node.js project created (package.json)',
|
|
109
|
+
auto:()=>existsSync(be('package.json'))?'PASS':'FAIL', guide:'Run: cd backend-project && npm init -y' },
|
|
110
|
+
{ id:'C22', cat:'Process (50%)', label:'Express.js installed',
|
|
111
|
+
auto:()=>existsSync(be('node_modules/express'))?'PASS':'FAIL', guide:'Run: cd backend-project && npm install express' },
|
|
112
|
+
{ id:'C23', cat:'Process (50%)', label:'Cors installed',
|
|
113
|
+
auto:()=>existsSync(be('node_modules/cors'))?'PASS':'FAIL', guide:'Run: cd backend-project && npm install cors' },
|
|
114
|
+
{ id:'C24', cat:'Process (50%)', label:'Nodemon installed',
|
|
115
|
+
auto:()=>existsSync(be('node_modules/nodemon'))?'PASS':'FAIL', guide:'Run: cd backend-project && npm install --save-dev nodemon' },
|
|
116
|
+
{ id:'C25', cat:'Process (50%)', label:'MySQL2 installed',
|
|
117
|
+
auto:()=>existsSync(be('node_modules/mysql2'))?'PASS':'FAIL', guide:'Run: cd backend-project && npm install mysql2' },
|
|
118
|
+
{ id:'C26', cat:'Process (50%)', label:'React project created (vite)',
|
|
119
|
+
auto:()=>existsSync(fe('package.json'))&&existsSync(fe('vite.config.js'))?'PASS':'FAIL', guide:'Run: npx create-vite frontend-project --template react' },
|
|
120
|
+
{ id:'C27', cat:'Process (50%)', label:'React-router-dom installed',
|
|
121
|
+
auto:()=>existsSync(fe('node_modules/react-router-dom'))?'PASS':'FAIL', guide:'Run: cd frontend-project && npm install react-router-dom' },
|
|
122
|
+
{ id:'C28', cat:'Process (50%)', label:'Axios installed',
|
|
123
|
+
auto:()=>existsSync(fe('node_modules/axios'))?'PASS':'FAIL', guide:'Run: cd frontend-project && npm install axios' },
|
|
124
|
+
{ id:'C29', cat:'Process (50%)', label:`${dbName} database SQL created`,
|
|
125
|
+
auto:()=>existsSync(b('database.sql'))&&read(b('database.sql')).includes(dbName)?'PASS':'FAIL', guide:'Create database.sql with CREATE DATABASE' },
|
|
126
|
+
{ id:'C30', cat:'Process (50%)', label:'Users table created in SQL',
|
|
127
|
+
auto:()=>read(b('database.sql')).includes('CREATE TABLE')&&read(b('database.sql')).toLowerCase().includes('user')?'PASS':'FAIL',
|
|
128
|
+
guide:'Add CREATE TABLE Users (UserID INT PK, Username, Password)' },
|
|
129
|
+
{ id:'C31', cat:'Process (50%)', label:`${projectType==='SIMS'?'Spare_Part':'Services'} table created`,
|
|
130
|
+
auto:()=>{const s=read(b('database.sql')).toLowerCase();return projectType==='SIMS'?(s.includes('spare_part')?'PASS':'FAIL'):(s.includes('services')?'PASS':'FAIL');},
|
|
131
|
+
guide:projectType==='SIMS'?'Add CREATE TABLE Spare_Part (SparePartID PK, Name, Category, Quantity, UnitPrice, TotalPrice)':'Add CREATE TABLE Services (ServiceCode PK, ServiceName, ServicePrice)' },
|
|
132
|
+
{ id:'C32', cat:'Process (50%)', label:`${projectType==='SIMS'?'Stock_Out':'ServiceRecord'} table created`,
|
|
133
|
+
auto:()=>{const s=read(b('database.sql')).toLowerCase();return projectType==='SIMS'?(s.includes('stock_out')?'PASS':'FAIL'):(s.includes('servicerecord')?'PASS':'FAIL');},
|
|
134
|
+
guide:projectType==='SIMS'?'Add CREATE TABLE Stock_Out':'Add CREATE TABLE ServiceRecord' },
|
|
135
|
+
{ id:'C33', cat:'Process (50%)', label:`${projectType==='SIMS'?'Stock_In':'Payment'} table created`,
|
|
136
|
+
auto:()=>{const s=read(b('database.sql')).toLowerCase();return projectType==='SIMS'?(s.includes('stock_in')?'PASS':'FAIL'):(s.includes('payment')?'PASS':'FAIL');},
|
|
137
|
+
guide:projectType==='SIMS'?'Add CREATE TABLE Stock_In':'Add CREATE TABLE Payment' },
|
|
138
|
+
{ id:'C34', cat:'Process (50%)', label:'Primary keys applied in all tables',
|
|
139
|
+
auto:()=>read(b('database.sql')).includes('PRIMARY KEY')?'PASS':'FAIL', guide:'Add PRIMARY KEY to each table definition' },
|
|
140
|
+
{ id:'C35', cat:'Process (50%)', label:'FKs applied in related tables',
|
|
141
|
+
auto:()=>read(b('database.sql')).includes('FOREIGN KEY')?'PASS':'FAIL', guide:'Add FOREIGN KEY references in child tables' },
|
|
142
|
+
{ id:'C36', cat:'Process (50%)', label:'React function component declared',
|
|
143
|
+
auto:()=>read(src('App.jsx')).includes('function')||read(src('App.jsx')).includes('=>')||read(src('App.jsx')).includes('export default function')?'PASS':'FAIL',
|
|
144
|
+
guide:'Use function App() { ... } in App.jsx' },
|
|
145
|
+
{ id:'C37', cat:'Process (50%)', label:'Return method included',
|
|
146
|
+
auto:()=>read(src('App.jsx')).includes('return')?'PASS':'FAIL', guide:'Add return( <JSX> ) in component' },
|
|
147
|
+
{ id:'C38', cat:'Process (50%)', label:'Component exported',
|
|
148
|
+
auto:()=>read(src('App.jsx')).includes('export default')?'PASS':'FAIL', guide:'Add "export default App;"' },
|
|
149
|
+
{ id:'C39', cat:'Process (50%)', label:'Component mounted to DOM',
|
|
150
|
+
auto:()=>existsSync(src('main.jsx'))&&read(src('main.jsx')).includes('ReactDOM')?'PASS':'FAIL',
|
|
151
|
+
guide:'In main.jsx: createRoot().render(<App />)' },
|
|
152
|
+
{ id:'C40', cat:'Process (50%)', label:'JSX used inside return',
|
|
153
|
+
auto:()=>read(src('App.jsx')).includes('return')&&(read(src('App.jsx')).includes('<')||read(src('App.jsx')).includes('React.createElement'))?'PASS':'FAIL',
|
|
154
|
+
guide:'Add JSX markup inside return()' },
|
|
155
|
+
{ id:'C41', cat:'Process (50%)', label:'Login form created',
|
|
156
|
+
auto:()=>existsSync(src('pages/Login.jsx'))?'PASS':'FAIL', guide:'Create src/pages/Login.jsx' },
|
|
157
|
+
{ id:'C42', cat:'Process (50%)', label:`${projectType==='SIMS'?'Spare_Part':'Cars/Services'} form created`,
|
|
158
|
+
auto:()=>{const p=inDir(src('pages'));return projectType==='SIMS'?(p.some(f=>f.toLowerCase().includes('spare'))?'PASS':'FAIL'):(p.some(f=>f.toLowerCase().includes('car'))&&p.some(f=>f.toLowerCase().includes('service'))?'PASS':'FAIL');},
|
|
159
|
+
guide:projectType==='SIMS'?'Create SparePart.jsx':'Create Cars.jsx and Services.jsx' },
|
|
160
|
+
{ id:'C43', cat:'Process (50%)', label:`${projectType==='SIMS'?'Stock_In':'ServiceRecord'} form created`,
|
|
161
|
+
auto:()=>inDir(src('pages')).some(f=>f.toLowerCase().includes(projectType==='SIMS'?'stockin':'record'))?'PASS':'FAIL',
|
|
162
|
+
guide:projectType==='SIMS'?'Create StockIn.jsx':'Create ServiceRecords.jsx' },
|
|
163
|
+
{ id:'C44', cat:'Process (50%)', label:`${projectType==='SIMS'?'Stock_Out':'Payment'} form created`,
|
|
164
|
+
auto:()=>inDir(src('pages')).some(f=>f.toLowerCase().includes(projectType==='SIMS'?'stockout':'payment'))?'PASS':'FAIL',
|
|
165
|
+
guide:projectType==='SIMS'?'Create StockOut.jsx':'Create Payments.jsx' },
|
|
166
|
+
{ id:'C45', cat:'Process (50%)', label:'Routes configured in React',
|
|
167
|
+
auto:()=>read(src('App.jsx')).includes('Routes')&&read(src('App.jsx')).includes('Route')?'PASS':'FAIL',
|
|
168
|
+
guide:'Add <Routes><Route ...></Routes> in App.jsx' },
|
|
169
|
+
{ id:'C46', cat:'Process (50%)', label:'Links created between components',
|
|
170
|
+
auto:()=>read(src('App.jsx')).includes('Link')||read(src('components/Navbar.jsx')).includes('Link')?'PASS':'FAIL',
|
|
171
|
+
guide:'Use <Link to="/path"> for navigation' },
|
|
172
|
+
{ id:'C47', cat:'Process (50%)', label:'Navigation layout created',
|
|
173
|
+
auto:()=>read(src('components/Navbar.jsx')).length>50?'PASS':'FAIL', guide:'Create Navbar.jsx' },
|
|
174
|
+
{ id:'C48', cat:'Process (50%)', label:'Tailwind CSS installed',
|
|
175
|
+
auto:()=>existsSync(fe('node_modules/tailwindcss'))?'PASS':'FAIL', guide:'Run: npm install -D tailwindcss' },
|
|
176
|
+
{ id:'C49', cat:'Process (50%)', label:'Tailwind CSS configured',
|
|
177
|
+
auto:()=>(existsSync(fe('tailwind.config.js'))&&read(fe('tailwind.config.js')).includes('content'))||read(fe('vite.config.js')).includes('tailwindcss')||read(fe('postcss.config.js')).includes('tailwind')?'PASS':'FAIL',
|
|
178
|
+
guide:'Configure tailwind.config.js or use @tailwindcss/vite plugin' },
|
|
179
|
+
{ id:'C50', cat:'Process (50%)', label:'Server JS file created',
|
|
180
|
+
auto:()=>existsSync(be('server.js'))?'PASS':'FAIL', guide:'Create server.js with express app' },
|
|
181
|
+
{ id:'C51', cat:'Process (50%)', label:'Express package imported',
|
|
182
|
+
auto:()=>read(be('server.js')).includes('express')?'PASS':'FAIL', guide:'const express = require(\'express\')' },
|
|
183
|
+
{ id:'C52', cat:'Process (50%)', label:'Cors package imported',
|
|
184
|
+
auto:()=>read(be('server.js')).includes('cors')?'PASS':'FAIL', guide:'const cors = require(\'cors\')' },
|
|
185
|
+
{ id:'C53', cat:'Process (50%)', label:'Port number identified',
|
|
186
|
+
auto:()=>read(be('server.js')).includes('PORT')?'PASS':'FAIL', guide:'const PORT = 5000' },
|
|
187
|
+
{ id:'C54', cat:'Process (50%)', label:'Express object created',
|
|
188
|
+
auto:()=>read(be('server.js')).includes('express()')?'PASS':'FAIL', guide:'const app = express()' },
|
|
189
|
+
{ id:'C55', cat:'Process (50%)', label:'Listen method applied',
|
|
190
|
+
auto:()=>read(be('server.js')).includes('listen')?'PASS':'FAIL', guide:'app.listen(PORT, callback)' },
|
|
191
|
+
{ id:'C56', cat:'Process (50%)', label:'POST endpoint created',
|
|
192
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('router.post'))?'PASS':'FAIL', guide:'Add router.post() in route files' },
|
|
193
|
+
{ id:'C57', cat:'Process (50%)', label:'GET endpoint created',
|
|
194
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('router.get'))?'PASS':'FAIL', guide:'Add router.get() in route files' },
|
|
195
|
+
{ id:'C58', cat:'Process (50%)', label:'PUT endpoint created',
|
|
196
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('router.put'))?'PASS':'FAIL', guide:'Add router.put() in route files' },
|
|
197
|
+
{ id:'C59', cat:'Process (50%)', label:'DELETE endpoint created',
|
|
198
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('router.delete'))?'PASS':'FAIL', guide:'Add router.delete() in route files' },
|
|
199
|
+
{ id:'C60', cat:'Process (50%)', label:'Axios imported in frontend',
|
|
200
|
+
auto:()=>read(src('App.jsx')).includes('axios')||existsSync(src('api/axios.js'))?'PASS':'FAIL', guide:'import axios from \'axios\'' },
|
|
201
|
+
{ id:'C61', cat:'Process (50%)', label:'bcryptjs installed (password encryption)',
|
|
202
|
+
auto:()=>existsSync(be('node_modules/bcryptjs'))?'PASS':'FAIL', guide:'npm install bcryptjs' },
|
|
203
|
+
{ id:'C62', cat:'Process (50%)', label:'Express-session installed',
|
|
204
|
+
auto:()=>existsSync(be('node_modules/express-session'))?'PASS':'FAIL', guide:'npm install express-session' },
|
|
205
|
+
{ id:'C63', cat:'Process (50%)', label:'Session-based login implemented',
|
|
206
|
+
auto:()=>read(be('server.js')).includes('session')&&read(be('routes/auth.js')).includes('session')?'PASS':'FAIL',
|
|
207
|
+
guide:'Use express-session with req.session.user' },
|
|
208
|
+
{ id:'C64', cat:'Process (50%)', label:'DB config file exists',
|
|
209
|
+
auto:()=>existsSync(be('config/db.js'))?'PASS':'FAIL', guide:'Create config/db.js with mysql2 pool' },
|
|
210
|
+
{ id:'C65', cat:'Process (50%)', label:'.env file configured',
|
|
211
|
+
auto:()=>existsSync(be('.env'))||existsSync(be('.env.example'))?'PASS':'FAIL', guide:'Create .env with DB_HOST, DB_USER, DB_PASSWORD, DB_NAME' },
|
|
212
|
+
{ id:'C66', cat:'Process (50%)', label:'Tailwind imported in index.css',
|
|
213
|
+
auto:()=>read(src('index.css')).includes('tailwind')?'PASS':'FAIL', guide:'@tailwind base; @tailwind components; @tailwind utilities;' },
|
|
214
|
+
{ id:'C67', cat:'Process (50%)', label:'Vite React plugin configured',
|
|
215
|
+
auto:()=>read(fe('vite.config.js')).includes('@vitejs/plugin-react')?'PASS':'FAIL', guide:'Add @vitejs/plugin-react in plugins' },
|
|
216
|
+
{ id:'C68', cat:'Product (30%)', label:'Project presentable',
|
|
217
|
+
auto:()=>'MANUAL', guide:'Prepare presentation: product name, key steps, importance, challenges' },
|
|
218
|
+
{ id:'C69', cat:'Product (30%)', label:'Input validation',
|
|
219
|
+
auto:()=>{const r=read(be('routes/stockOut.js'))||read(be('routes/payments.js'));return r.includes('return res.status(400)')?'PASS':'FAIL';},
|
|
220
|
+
guide:'Add if(!field) return res.status(400).json(...)' },
|
|
221
|
+
{ id:'C70', cat:'Product (30%)', label:'Form saves data to DB',
|
|
222
|
+
auto:()=>{const r=inDir(be('routes'));return r.some(f=>read(be('routes/'+f)).includes('INSERT INTO')||read(be('routes/'+f)).includes('res.status(201)'))?'PASS':'FAIL';},
|
|
223
|
+
guide:'Use INSERT INTO in POST handlers' },
|
|
224
|
+
{ id:'C71', cat:'Product (30%)', label:'Data retrieval works (GET)',
|
|
225
|
+
auto:()=>{const r=inDir(be('routes'));return r.some(f=>read(be('routes/'+f)).includes('SELECT')&&read(be('routes/'+f)).includes('router.get'))?'PASS':'FAIL';},
|
|
226
|
+
guide:'Use SELECT in GET handlers and return JSON' },
|
|
227
|
+
{ id:'C72', cat:'Product (30%)', label:'Update works (PUT)',
|
|
228
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('UPDATE')&&read(be('routes/'+f)).includes('router.put'))?'PASS':'FAIL',
|
|
229
|
+
guide:'Use UPDATE in PUT handlers' },
|
|
230
|
+
{ id:'C73', cat:'Product (30%)', label:'Delete works (DELETE)',
|
|
231
|
+
auto:()=>inDir(be('routes')).some(f=>read(be('routes/'+f)).includes('DELETE')&&read(be('routes/'+f)).includes('router.delete'))?'PASS':'FAIL',
|
|
232
|
+
guide:'Use DELETE in DELETE handlers' },
|
|
233
|
+
{ id:'C74', cat:'Product (30%)', label:'Navigation menus interactive',
|
|
234
|
+
auto:()=>read(src('components/Navbar.jsx')).includes('useState')&&read(src('components/Navbar.jsx')).includes('onClick')?'PASS':'FAIL',
|
|
235
|
+
guide:'Add onClick handlers and hamburger menu' },
|
|
236
|
+
{ id:'C75', cat:'Product (30%)', label:'Buttons interactive',
|
|
237
|
+
auto:()=>(read(src('pages/Login.jsx')).includes('onClick')||read(src('pages/Login.jsx')).includes('onSubmit'))?'PASS':'FAIL',
|
|
238
|
+
guide:'Add onClick or onSubmit to buttons' },
|
|
239
|
+
{ id:'C76', cat:'Product (30%)', label:'Responsive design (flexbox/grid)',
|
|
240
|
+
auto:()=>(read(src('components/Navbar.jsx')).includes('md:')||read(src('App.jsx')).includes('flex')||read(src('index.css')).includes('grid'))?'PASS':'FAIL',
|
|
241
|
+
guide:'Use Tailwind responsive prefixes (sm:, md:, lg:)' },
|
|
242
|
+
{ id:'C77', cat:'Product (30%)', label:`Daily ${projectType==='SIMS'?'StockOut':'Service'} report`,
|
|
243
|
+
auto:()=>read(be('routes/reports.js')).includes(projectType==='SIMS'?'daily-stockout':'/daily')?'PASS':'FAIL',
|
|
244
|
+
guide:`Add GET /api/reports/${projectType==='SIMS'?'daily-stockout':'daily'} endpoint` },
|
|
245
|
+
{ id:'C78', cat:'Product (30%)', label:'Stock Status / Summary report',
|
|
246
|
+
auto:()=>read(be('routes/reports.js')).includes('stock-status')||read(be('routes/reports.js')).includes('summary')?'PASS':'FAIL',
|
|
247
|
+
guide:'Add GET /api/reports/stock-status or /api/reports/summary' },
|
|
248
|
+
{ id:'C79', cat:'Product (30%)', label:'Consistent styling',
|
|
249
|
+
auto:()=>read(src('index.css')).length>0&&(read(fe('tailwind.config.js')||'')).length>0?'PASS':'FAIL',
|
|
250
|
+
guide:'Use consistent color palette in Tailwind config' },
|
|
251
|
+
{ id:'C80', cat:'Closing (5%)', label:'Can remove project folder after marking',
|
|
252
|
+
auto:()=>'MANUAL', guide:'After assessor approval, delete project folder' },
|
|
253
|
+
{ id:'C81', cat:'Closing (5%)', label:'Can delete database after marking',
|
|
254
|
+
auto:()=>'MANUAL', guide:'Run: DROP DATABASE '+dbName+';' },
|
|
255
|
+
{ id:'C82', cat:'Closing (5%)', label:'Global dependencies removable',
|
|
256
|
+
auto:()=>'MANUAL', guide:'npm uninstall -g nodemon etc.' },
|
|
289
257
|
];
|
|
290
258
|
return checks;
|
|
291
259
|
}
|
|
292
260
|
|
|
293
261
|
function showResults(results) {
|
|
294
|
-
const groups
|
|
295
|
-
for
|
|
296
|
-
if (!groups[r.cat]) groups[r.cat] = [];
|
|
297
|
-
groups[r.cat].push(r);
|
|
298
|
-
}
|
|
299
|
-
|
|
262
|
+
const groups={};
|
|
263
|
+
for(const r of results){if(!groups[r.cat])groups[r.cat]=[];groups[r.cat].push(r);}
|
|
300
264
|
log(chalk.bold('\n═══════════════════════════════════════════════════════════════'));
|
|
301
265
|
log(chalk.bold(' AUTO-ASSESSMENT RESULTS'));
|
|
302
266
|
log(chalk.bold('═══════════════════════════════════════════════════════════════\n'));
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (r.status === 'PASS') { passed++;
|
|
312
|
-
log(` ${chalk.green('✓')} ${r.label.padEnd(58)} ${chalk.green('PASS')}`); }
|
|
313
|
-
else if (r.status === 'FAIL') { failed++;
|
|
314
|
-
log(` ${chalk.red('✗')} ${r.label.padEnd(58)} ${chalk.red('FAIL')}`); }
|
|
315
|
-
else { manual++;
|
|
316
|
-
log(` ${chalk.cyan('?')} ${r.label.padEnd(58)} ${chalk.cyan('MANUAL')}`); }
|
|
317
|
-
}
|
|
318
|
-
log('');
|
|
267
|
+
let total=results.length,passed=0,failed=0,manual=0;
|
|
268
|
+
for(const[cat,items]of Object.entries(groups)){
|
|
269
|
+
log(chalk.bold(chalk.bgHex('#1e293b').white(` ${cat} `)));log('');
|
|
270
|
+
for(const r of items){
|
|
271
|
+
if(r.status==='PASS'){passed++;log(` ${chalk.green('✓')} ${r.label.padEnd(55)} ${chalk.green('PASS')}`);}
|
|
272
|
+
else if(r.status==='FAIL'){failed++;log(` ${chalk.red('✗')} ${r.label.padEnd(55)} ${chalk.red('FAIL')}`);}
|
|
273
|
+
else{manual++;log(` ${chalk.cyan('?')} ${r.label.padEnd(55)} ${chalk.cyan('MANUAL')}`);}
|
|
274
|
+
}log('');
|
|
319
275
|
}
|
|
320
|
-
|
|
321
276
|
log(chalk.bold('───────────────────────────────────────────────────────────────'));
|
|
322
|
-
log(chalk.bold(` ${chalk.green('PASSED')}
|
|
323
|
-
const autoTotal
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (failed > 0) {
|
|
329
|
-
log(chalk.bold(chalk.yellow('\n─── HOW TO FIX FAILED ITEMS ───\n')));
|
|
330
|
-
for (const r of results) {
|
|
331
|
-
if (r.status === 'FAIL' && r.guide) {
|
|
332
|
-
log(` ${chalk.red('✗')} ${chalk.bold(r.label)}`);
|
|
333
|
-
log(` ${chalk.dim('→')} ${r.guide}\n`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
277
|
+
log(chalk.bold(` ${chalk.green('PASSED')}:${passed} ${chalk.red('FAILED')}:${failed} ${chalk.cyan('MANUAL')}:${manual} TOTAL:${total}`));
|
|
278
|
+
const autoTotal=total-manual,autoPassed=passed;
|
|
279
|
+
log(chalk.bold(` Auto-check score: ${autoTotal>0?((autoPassed/autoTotal)*100).toFixed(1):'N/A'}%`));
|
|
280
|
+
if(failed>0){
|
|
281
|
+
log(chalk.bold(chalk.yellow('\n─── FIX FAILED ITEMS ───\n')));
|
|
282
|
+
for(const r of results){if(r.status==='FAIL'&&r.guide)log(` ${chalk.red('✗')} ${chalk.bold(r.label)}\n ${chalk.dim('→')} ${r.guide}\n`);}
|
|
336
283
|
}
|
|
337
|
-
|
|
338
284
|
log(chalk.bold('═══════════════════════════════════════════════════════════════\n'));
|
|
339
285
|
}
|
|
340
286
|
|
|
341
|
-
function generateMermaidDiagrams(projectType)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
287
|
+
function generateMermaidDiagrams(projectType){
|
|
288
|
+
if(projectType==='SIMS'){
|
|
289
|
+
return {
|
|
290
|
+
erd:`erDiagram
|
|
345
291
|
Users ||--o{ Stock_Out : "records"
|
|
346
292
|
Spare_Part ||--o{ Stock_In : "receives"
|
|
347
293
|
Spare_Part ||--o{ Stock_Out : "issues"
|
|
348
|
-
|
|
349
|
-
Users {
|
|
350
|
-
int UserID PK "Auto Increment"
|
|
294
|
+
Users { int UserID PK "Auto Increment"
|
|
351
295
|
varchar Username "Unique"
|
|
352
|
-
varchar Password "Hashed"
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
Spare_Part {
|
|
356
|
-
int SparePartID PK "Auto Increment"
|
|
296
|
+
varchar Password "Hashed" }
|
|
297
|
+
Spare_Part { int SparePartID PK "Auto Increment"
|
|
357
298
|
varchar Name
|
|
358
299
|
varchar Category
|
|
359
300
|
int Quantity
|
|
360
301
|
decimal UnitPrice
|
|
361
|
-
decimal TotalPrice "
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
Stock_In {
|
|
365
|
-
int StockInID PK "Auto Increment"
|
|
302
|
+
decimal TotalPrice "Qty * UnitPrice" }
|
|
303
|
+
Stock_In { int StockInID PK "Auto Increment"
|
|
366
304
|
int SparePartID FK
|
|
367
305
|
int StockInQuantity
|
|
368
|
-
date StockInDate
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
Stock_Out {
|
|
372
|
-
int StockOutID PK "Auto Increment"
|
|
306
|
+
date StockInDate }
|
|
307
|
+
Stock_Out { int StockOutID PK "Auto Increment"
|
|
373
308
|
int SparePartID FK
|
|
374
309
|
int UserID FK
|
|
375
310
|
int StockOutQuantity
|
|
376
311
|
decimal StockOutUnitPrice
|
|
377
312
|
decimal StockOutTotalPrice
|
|
378
|
-
date StockOutDate
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
dfd = `graph TD
|
|
313
|
+
date StockOutDate }`,
|
|
314
|
+
dfd:`graph TD
|
|
382
315
|
subgraph "Context Level 0"
|
|
383
316
|
SM[Stock Manager] -->|Input Data| SIMS[SIMS System]
|
|
384
|
-
SIMS -->|Reports| SM
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
subgraph "Level 1 - Processes"
|
|
317
|
+
SIMS -->|Reports| SM end
|
|
318
|
+
subgraph "Processes"
|
|
388
319
|
P1["1.0 Manage Spare Parts"]
|
|
389
320
|
P2["2.0 Record Stock In"]
|
|
390
321
|
P3["3.0 Record Stock Out"]
|
|
391
322
|
P4["4.0 Generate Reports"]
|
|
392
|
-
P5["5.0 Authentication"]
|
|
393
|
-
end
|
|
394
|
-
|
|
323
|
+
P5["5.0 Authentication"] end
|
|
395
324
|
subgraph "Data Stores"
|
|
396
|
-
D1[(Spare_Part)]
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
P1 --> D1
|
|
406
|
-
SM -->|"Stock In Data"| P2
|
|
407
|
-
P2 --> D2
|
|
408
|
-
P2 -->|"Update Stock"| D1
|
|
409
|
-
SM -->|"Stock Out Data"| P3
|
|
410
|
-
P3 --> D3
|
|
411
|
-
P3 -->|"Reduce Stock"| D1
|
|
412
|
-
P3 -->|"Record User"| D4
|
|
413
|
-
SM -->|"Request Report"| P4
|
|
414
|
-
P4 --> D1
|
|
415
|
-
P4 --> D2
|
|
416
|
-
P4 --> D3`;
|
|
417
|
-
} else {
|
|
418
|
-
erd = `erDiagram
|
|
325
|
+
D1[(Spare_Part)] D2[(Stock_In)] D3[(Stock_Out)] D4[(Users)] end
|
|
326
|
+
SM-->|"Login"|P5 P5-->D4
|
|
327
|
+
SM-->|"Spare Part"|P1 P1-->D1
|
|
328
|
+
SM-->|"Stock In"|P2 P2-->D2 P2-->|"Update"|D1
|
|
329
|
+
SM-->|"Stock Out"|P3 P3-->D3 P3-->|"Reduce"|D1 P3-->|"User"|D4
|
|
330
|
+
SM-->|"Report"|P4 P4-->D1 P4-->D2 P4-->D3`};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
erd:`erDiagram
|
|
419
334
|
Car ||--o{ ServiceRecord : "has"
|
|
420
335
|
Services ||--o{ ServiceRecord : "includes"
|
|
421
336
|
ServiceRecord ||--o{ Payment : "generates"
|
|
422
337
|
User ||--o{ Payment : "processes"
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
varchar
|
|
426
|
-
|
|
427
|
-
varchar
|
|
428
|
-
|
|
429
|
-
varchar
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
varchar
|
|
435
|
-
|
|
436
|
-
decimal ServicePrice
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
ServiceRecord {
|
|
440
|
-
int RecordNumber PK "Auto Increment"
|
|
441
|
-
date ServiceDate
|
|
442
|
-
varchar PlateNumber FK
|
|
443
|
-
varchar ServiceCode FK
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
Payment {
|
|
447
|
-
int PaymentNumber PK "Auto Increment"
|
|
448
|
-
decimal AmountPaid
|
|
449
|
-
date PaymentDate
|
|
450
|
-
int RecordNumber FK
|
|
451
|
-
int UserID FK
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
User {
|
|
455
|
-
int UserID PK "Auto Increment"
|
|
456
|
-
varchar Username "Unique"
|
|
457
|
-
varchar Password "Hashed"
|
|
458
|
-
varchar FullName
|
|
459
|
-
}`;
|
|
460
|
-
|
|
461
|
-
dfd = `graph TD
|
|
338
|
+
Car { varchar PlateNumber PK
|
|
339
|
+
varchar type varchar Model int ManufacturingYear
|
|
340
|
+
varchar DriverPhone varchar MechanicName }
|
|
341
|
+
Services { varchar ServiceCode PK
|
|
342
|
+
varchar ServiceName decimal ServicePrice }
|
|
343
|
+
ServiceRecord { int RecordNumber PK "Auto Increment"
|
|
344
|
+
date ServiceDate varchar PlateNumber FK varchar ServiceCode FK }
|
|
345
|
+
Payment { int PaymentNumber PK "Auto Increment"
|
|
346
|
+
decimal AmountPaid date PaymentDate
|
|
347
|
+
int RecordNumber FK int UserID FK }
|
|
348
|
+
User { int UserID PK "Auto Increment"
|
|
349
|
+
varchar Username "Unique" varchar Password "Hashed" varchar FullName }`,
|
|
350
|
+
dfd:`graph TD
|
|
462
351
|
subgraph "Context Level 0"
|
|
463
|
-
CM[Cashier
|
|
464
|
-
CRPMS -->|Reports & Bills| CM
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
P2["2.0 Manage Services"]
|
|
470
|
-
P3["3.0 Record Service Records"]
|
|
471
|
-
P4["4.0 Record Payments"]
|
|
472
|
-
P5["5.0 Generate Reports & Bills"]
|
|
473
|
-
P6["6.0 Authentication"]
|
|
474
|
-
end
|
|
475
|
-
|
|
352
|
+
CM[Cashier] -->|Input| CRPMS[CRPMS System]
|
|
353
|
+
CRPMS -->|Reports & Bills| CM end
|
|
354
|
+
subgraph "Processes"
|
|
355
|
+
P1["1.0 Manage Cars"] P2["2.0 Manage Services"]
|
|
356
|
+
P3["3.0 Service Records"] P4["4.0 Payments"]
|
|
357
|
+
P5["5.0 Reports & Bills"] P6["6.0 Authentication"] end
|
|
476
358
|
subgraph "Data Stores"
|
|
477
|
-
D1[(Car)]
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
CM -->|"Login"| P6
|
|
485
|
-
P6 --> D5
|
|
486
|
-
CM -->|"Car Details"| P1
|
|
487
|
-
P1 --> D1
|
|
488
|
-
CM -->|"Service Types"| P2
|
|
489
|
-
P2 --> D2
|
|
490
|
-
CM -->|"Service Record"| P3
|
|
491
|
-
P3 --> D1
|
|
492
|
-
P3 --> D2
|
|
493
|
-
P3 --> D3
|
|
494
|
-
CM -->|"Payment Data"| P4
|
|
495
|
-
P4 --> D3
|
|
496
|
-
P4 --> D4
|
|
497
|
-
P4 --> D5
|
|
498
|
-
CM -->|"Request Report/Bill"| P5
|
|
499
|
-
P5 --> D1
|
|
500
|
-
P5 --> D2
|
|
501
|
-
P5 --> D3
|
|
502
|
-
P5 --> D4`;
|
|
503
|
-
}
|
|
504
|
-
return { erd, dfd };
|
|
359
|
+
D1[(Car)] D2[(Services)] D3[(ServiceRecord)] D4[(Payment)] D5[(User)] end
|
|
360
|
+
CM-->|"Login"|P6 P6-->D5
|
|
361
|
+
CM-->|"Car"|P1 P1-->D1
|
|
362
|
+
CM-->|"Service"|P2 P2-->D2
|
|
363
|
+
CM-->|"Record"|P3 P3-->D1 P3-->D2 P3-->D3
|
|
364
|
+
CM-->|"Payment"|P4 P4-->D3 P4-->D4 P4-->D5
|
|
365
|
+
CM-->|"Report"|P5 P5-->D1 P5-->D2 P5-->D3 P5-->D4`};
|
|
505
366
|
}
|
|
506
367
|
|
|
507
|
-
function initMermaidFile(
|
|
508
|
-
const
|
|
509
|
-
const title
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
368
|
+
function initMermaidFile(fp, pt){
|
|
369
|
+
const d=generateMermaidDiagrams(pt);
|
|
370
|
+
const title=pt==='SIMS'?'Stock Inventory Management System (SIMS)':'Car Repair Payment Management System (CRPMS)';
|
|
371
|
+
const html=`<!DOCTYPE html>
|
|
372
|
+
<html lang="en">
|
|
373
|
+
<head>
|
|
374
|
+
<meta charset="UTF-8">
|
|
375
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
376
|
+
<title>${title} - ERD & DFD</title>
|
|
377
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
378
|
+
<style>
|
|
379
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
380
|
+
body{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;padding:40px 20px;display:flex;flex-direction:column;align-items:center}
|
|
381
|
+
.container{max-width:1200px;width:100%}
|
|
382
|
+
h1{text-align:center;color:#1a1a2e;margin-bottom:8px;font-size:28px}
|
|
383
|
+
.subtitle{text-align:center;color:#666;margin-bottom:40px;font-size:14px}
|
|
384
|
+
.section{background:white;border-radius:16px;padding:32px;margin-bottom:32px;box-shadow:0 2px 12px rgba(0,0,0,0.08)}
|
|
385
|
+
.section h2{color:#1a1a2e;margin-bottom:20px;padding-bottom:12px;border-bottom:3px solid #00D2FF;font-size:20px}
|
|
386
|
+
.mermaid-wrapper{overflow-x:auto;padding:8px 0}
|
|
387
|
+
.mermaid svg{max-width:100%;height:auto}
|
|
388
|
+
.footer{text-align:center;color:#999;font-size:12px;margin-top:16px}
|
|
389
|
+
</style>
|
|
390
|
+
</head>
|
|
391
|
+
<body>
|
|
392
|
+
<div class="container">
|
|
393
|
+
<h1>${title}</h1>
|
|
394
|
+
<p class="subtitle">Entity Relationship Diagram & Data Flow Diagram — Generated by npms-exam-kit on ${new Date().toISOString().split('T')[0]}</p>
|
|
395
|
+
|
|
396
|
+
<div class="section">
|
|
397
|
+
<h2>Entity Relationship Diagram (ERD)</h2>
|
|
398
|
+
<div class="mermaid-wrapper">
|
|
399
|
+
<pre class="mermaid">${d.erd}</pre>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<div class="section">
|
|
404
|
+
<h2>Data Flow Diagram (DFD)</h2>
|
|
405
|
+
<div class="mermaid-wrapper">
|
|
406
|
+
<pre class="mermaid">${d.dfd}</pre>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
<p class="footer">Open this file in a browser to view interactive diagrams. Generated by npms-exam-kit</p>
|
|
411
|
+
</div>
|
|
412
|
+
</body>
|
|
413
|
+
</html>`;
|
|
414
|
+
writeFileSync(fp.replace(/\.md$/,'.html'),html,'utf8');
|
|
415
|
+
return fp.replace(/\.md$/,'.html');
|
|
416
|
+
}
|
|
528
417
|
|
|
529
|
-
|
|
418
|
+
function showNextSteps(project, targetDir){
|
|
419
|
+
const dbName=project==='SIMS'?'SIMS':'CRPMS';
|
|
420
|
+
const dbUser=project==='SIMS'?'admin / admin123':'admin / Admin@1234';
|
|
421
|
+
const endpoints=project==='SIMS'?SIMS_ENDPOINTS:CRPMS_ENDPOINTS;
|
|
422
|
+
const pTitle=project==='SIMS'?'Spare Part':'Car Repair Payment';
|
|
423
|
+
|
|
424
|
+
log(chalk.bold.hex('#00D2FF')('\n═══════════════════════════════════════════════════════════════'));
|
|
425
|
+
log(chalk.bold.hex('#00D2FF')(' NEXT STEPS - HOW TO RUN EVERYTHING'));
|
|
426
|
+
log(chalk.bold.hex('#00D2FF')('═══════════════════════════════════════════════════════════════\n'));
|
|
427
|
+
|
|
428
|
+
log(chalk.bold(chalk.bgGreen(' STEP 1: IMPORT DATABASE VIA XAMPP ')));
|
|
429
|
+
log(`\n ${chalk.cyan('A)')} Open XAMPP Control Panel → Start ${chalk.bold('Apache')} & ${chalk.bold('MySQL')}`);
|
|
430
|
+
log(` ${chalk.cyan('B)')} Open browser → ${chalk.bold('http://localhost/phpmyadmin')}`);
|
|
431
|
+
log(` ${chalk.cyan('C)')} Click ${chalk.bold('"New"')} in left sidebar`);
|
|
432
|
+
log(` ${chalk.cyan('D)')} Database name: ${chalk.bold(dbName)} → Click ${chalk.bold('"Create"')}`);
|
|
433
|
+
log(` ${chalk.cyan('E)')} Click ${chalk.bold('"SQL"')} tab`);
|
|
434
|
+
log(` ${chalk.cyan('F)')} Open this file in Notepad:`);
|
|
435
|
+
log(` ${chalk.dim(join(targetDir, 'database.sql'))}`);
|
|
436
|
+
log(` ${chalk.cyan('G)')} ${chalk.bold('Copy ALL the SQL code')} and paste into phpMyAdmin SQL box`);
|
|
437
|
+
log(` ${chalk.cyan('H)')} Click ${chalk.bold('"Go"')} to execute`);
|
|
438
|
+
log(` ${chalk.green(' ✓ Database and tables will be created automatically')}\n`);
|
|
439
|
+
|
|
440
|
+
log(chalk.bold(chalk.bgGreen(' STEP 2: CONFIGURE BACKEND .ENV ')));
|
|
441
|
+
log(`\n Open ${chalk.bold(join(targetDir, 'backend-project', '.env'))} and verify:`);
|
|
442
|
+
log(` ${chalk.dim('DB_HOST=localhost')}`);
|
|
443
|
+
log(` ${chalk.dim('DB_USER=root')}`);
|
|
444
|
+
log(` ${chalk.dim('DB_PASSWORD= (leave empty if no password)')}`);
|
|
445
|
+
log(` ${chalk.dim('DB_NAME='+dbName)}`);
|
|
446
|
+
log(` ${chalk.dim('PORT=5000')}\n`);
|
|
447
|
+
|
|
448
|
+
log(chalk.bold(chalk.bgGreen(' STEP 3: START BACKEND SERVER ')));
|
|
449
|
+
log(`\n Open a terminal and run:`);
|
|
450
|
+
log(` ${chalk.cyan(' cd "' + join(targetDir, 'backend-project') + '"')}`);
|
|
451
|
+
log(` ${chalk.cyan(' npm start')}`);
|
|
452
|
+
log(`\n ${chalk.green(' ✓ Backend will run on:')} ${chalk.bold('http://localhost:5000')}\n`);
|
|
453
|
+
|
|
454
|
+
log(chalk.bold(chalk.bgGreen(' STEP 4: START FRONTEND SERVER ')));
|
|
455
|
+
log(`\n Open a SECOND terminal and run:`);
|
|
456
|
+
log(` ${chalk.cyan(' cd "' + join(targetDir, 'frontend-project') + '"')}`);
|
|
457
|
+
log(` ${chalk.cyan(' npm run dev')}`);
|
|
458
|
+
log(`\n ${chalk.green(' ✓ Frontend will run on:')} ${chalk.bold('http://localhost:5173')}\n`);
|
|
459
|
+
|
|
460
|
+
log(chalk.bold(chalk.bgGreen(' STEP 5: LOGIN ')));
|
|
461
|
+
log(`\n Open ${chalk.bold('http://localhost:5173')} in your browser`);
|
|
462
|
+
log(` Login with: ${chalk.bold(dbUser)}\n`);
|
|
463
|
+
|
|
464
|
+
log(chalk.bold(chalk.bgGreen(' STEP 6: TEST API ENDPOINTS WITH POSTMAN ')));
|
|
465
|
+
log(`\n First, make sure the backend is running (Step 3).`);
|
|
466
|
+
log(` Then open Postman and test these endpoints:\n`);
|
|
467
|
+
|
|
468
|
+
for(const ep of endpoints){
|
|
469
|
+
const mc=ep.method==='POST'?chalk.yellow:ep.method==='PUT'?chalk.blue:ep.method==='DELETE'?chalk.red:chalk.green;
|
|
470
|
+
log(` ${mc(ep.method.padEnd(6))} ${chalk.bold('http://localhost:5000'+ep.path)}`);
|
|
471
|
+
log(` ${chalk.dim(ep.desc)}`);
|
|
472
|
+
if(ep.body) log(` ${chalk.dim('Body (raw JSON):')} ${ep.body}`);
|
|
473
|
+
log('');
|
|
474
|
+
}
|
|
530
475
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
476
|
+
log(chalk.bold(chalk.bgGreen(' STEP 7: POSTMAN TESTING TIPS ')));
|
|
477
|
+
log(`\n ${chalk.cyan('•')} For POST/PUT requests: select ${chalk.bold('Body → raw → JSON')}`);
|
|
478
|
+
log(` ${chalk.cyan('•')} The session cookie is handled automatically by Postman`);
|
|
479
|
+
log(` ${chalk.cyan('•')} Test login first, then other endpoints`);
|
|
480
|
+
log(` ${chalk.cyan('•')} For GET requests: you can paste the URL directly in browser too`);
|
|
481
|
+
|
|
482
|
+
log(chalk.bold('\n───────────────────────────────────────────────────────────────'));
|
|
483
|
+
log(chalk.bold(` PROJECT: ${project==='SIMS'?'SIMS - Stock Inventory Mgmt':'CRPMS - Car Repair Payment Mgmt'}`));
|
|
484
|
+
log(chalk.bold(` LOCATION: ${chalk.dim(targetDir)}`));
|
|
485
|
+
log(chalk.bold(` DATABASE: ${chalk.dim(dbName)}`));
|
|
486
|
+
log(chalk.bold(` FRONTEND: ${chalk.dim('http://localhost:5173')}`));
|
|
487
|
+
log(chalk.bold(` BACKEND: ${chalk.dim('http://localhost:5000')}`));
|
|
488
|
+
log(chalk.bold(` Diagrams (ERD & DFD): ${chalk.dim(join(targetDir, project+'_ERD_DFD.html'))}`));
|
|
489
|
+
log(chalk.bold(` Assessment report saved: ${chalk.dim(join(targetDir, 'checklist_report'))}`));
|
|
490
|
+
log(chalk.bold('───────────────────────────────────────────────────────────────\n'));
|
|
491
|
+
|
|
492
|
+
log(chalk.dim(' NOTE: Close XAMPP MySQL before running "npm start" if using XAMPP,'));
|
|
493
|
+
log(chalk.dim(' or change the port in XAMPP config to avoid port conflicts.\n'));
|
|
535
494
|
}
|
|
536
495
|
|
|
537
|
-
function
|
|
538
|
-
|
|
539
|
-
async function main() {
|
|
496
|
+
async function main(){
|
|
540
497
|
console.clear();
|
|
541
498
|
log(chalk.bold.hex('#00D2FF')('\n ╔══════════════════════════════════════════════════╗'));
|
|
542
499
|
log(chalk.bold.hex('#00D2FF')(' ║ NESA NATIONAL PRACTICAL EXAM 2024-2025 ║'));
|
|
@@ -544,189 +501,89 @@ async function main() {
|
|
|
544
501
|
log(chalk.bold.hex('#00D2FF')(' ║ TRADE: Software Development ║'));
|
|
545
502
|
log(chalk.bold.hex('#00D2FF')(' ╚══════════════════════════════════════════════════╝\n'));
|
|
546
503
|
|
|
547
|
-
const {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
{ name: 'CRPMS - Car Repair Payment Management System', value: 'CRPMS' },
|
|
565
|
-
]
|
|
566
|
-
}]);
|
|
567
|
-
|
|
568
|
-
const targetDir = join(process.cwd());
|
|
569
|
-
|
|
570
|
-
if (mode === 'mermaid') {
|
|
571
|
-
const filePath = join(targetDir, `${project}_ERD_DFD.md`);
|
|
572
|
-
initMermaidFile(filePath, project);
|
|
573
|
-
log(chalk.green(`\n ✓ Mermaid diagrams saved to: ${chalk.bold(filePath)}\n`));
|
|
574
|
-
log(chalk.dim(' Open this file in any Markdown viewer that supports Mermaid'));
|
|
575
|
-
log(chalk.dim(' (VS Code, GitHub, or https://mermaid.live)\n'));
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (mode === 'assess') {
|
|
580
|
-
log(chalk.bold(`\n Running auto-assessment for ${project} in: ${targetDir}\n`));
|
|
581
|
-
const checks = buildChecklist(targetDir, project);
|
|
582
|
-
const results = checks.map(c => {
|
|
583
|
-
let status;
|
|
584
|
-
try {
|
|
585
|
-
const result = c.auto();
|
|
586
|
-
status = result === true || result === 'PASS' ? 'PASS' : (result === 'MANUAL' ? 'MANUAL' : 'FAIL');
|
|
587
|
-
} catch { status = 'MANUAL'; }
|
|
588
|
-
return { ...c, status, guide: c.guide || '' };
|
|
589
|
-
});
|
|
590
|
-
showResults(results);
|
|
591
|
-
const reportDir = join(targetDir, 'checklist_report');
|
|
592
|
-
mkdirSync(reportDir, { recursive: true });
|
|
593
|
-
const reportFile = join(reportDir, `assessment_${project}_${Date.now()}.json`);
|
|
594
|
-
writeFileSync(reportFile, JSON.stringify({
|
|
595
|
-
project, date: new Date().toISOString(),
|
|
596
|
-
total: results.length,
|
|
597
|
-
passed: results.filter(r => r.status === 'PASS').length,
|
|
598
|
-
failed: results.filter(r => r.status === 'FAIL').length,
|
|
599
|
-
manual: results.filter(r => r.status === 'MANUAL').length,
|
|
600
|
-
results
|
|
601
|
-
}, null, 2));
|
|
602
|
-
log(chalk.dim(` Report saved: ${reportFile}\n`));
|
|
603
|
-
|
|
604
|
-
const { genDiag } = await inquirer.prompt([{
|
|
605
|
-
type: 'confirm', name: 'genDiag',
|
|
606
|
-
message: 'Generate ERD & DFD Mermaid diagrams for this project?',
|
|
607
|
-
default: true
|
|
608
|
-
}]);
|
|
609
|
-
if (genDiag) {
|
|
610
|
-
const filePath = join(targetDir, `${project}_ERD_DFD.md`);
|
|
611
|
-
initMermaidFile(filePath, project);
|
|
612
|
-
log(chalk.green(` ✓ Diagrams saved to: ${chalk.bold(filePath)}\n`));
|
|
613
|
-
}
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// INSTALL mode
|
|
618
|
-
const exist = [];
|
|
619
|
-
if (existsSync(join(targetDir, 'backend-project'))) exist.push('backend-project');
|
|
620
|
-
if (existsSync(join(targetDir, 'frontend-project'))) exist.push('frontend-project');
|
|
621
|
-
|
|
622
|
-
if (exist.length > 0) {
|
|
623
|
-
log(chalk.yellow(` Warning: ${exist.join(', ')} already exists.`));
|
|
624
|
-
const { overwrite } = await inquirer.prompt([{
|
|
625
|
-
type: 'confirm', name: 'overwrite',
|
|
626
|
-
message: 'Overwrite existing files?', default: false
|
|
627
|
-
}]);
|
|
628
|
-
if (!overwrite) { log(chalk.yellow(' Cancelled.')); process.exit(0); }
|
|
629
|
-
for (const f of ['backend-project', 'frontend-project', 'database.sql']) {
|
|
630
|
-
const p = join(targetDir, f);
|
|
631
|
-
if (existsSync(p)) fs.removeSync(p);
|
|
632
|
-
}
|
|
504
|
+
const {project}=await inquirer.prompt([{type:'list',name:'project',message:'Select project:',
|
|
505
|
+
choices:[
|
|
506
|
+
{name:'SIMS - Stock Inventory Management System (SmartPark)',value:'SIMS'},
|
|
507
|
+
{name:'CRPMS - Car Repair Payment Management System',value:'CRPMS'},
|
|
508
|
+
]}]);
|
|
509
|
+
|
|
510
|
+
const targetDir=join(process.cwd());
|
|
511
|
+
|
|
512
|
+
// Check if project already installed
|
|
513
|
+
const exist=[];
|
|
514
|
+
if(existsSync(join(targetDir,'backend-project')))exist.push('backend-project');
|
|
515
|
+
if(existsSync(join(targetDir,'frontend-project')))exist.push('frontend-project');
|
|
516
|
+
if(exist.length>0){
|
|
517
|
+
log(chalk.yellow(` Warning: ${exist.join(', ')} already exists here.`));
|
|
518
|
+
const {overwrite}=await inquirer.prompt([{type:'confirm',name:'overwrite',message:'Overwrite?',default:false}]);
|
|
519
|
+
if(!overwrite){log(chalk.yellow(' Cancelled.'));process.exit(0);}
|
|
520
|
+
for(const f of['backend-project','frontend-project','database.sql']){const p=join(targetDir,f);if(existsSync(p))fs.removeSync(p);}
|
|
633
521
|
}
|
|
634
522
|
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
if (!existsSync(srcDir)) { log(chalk.red(` Error: Source ${srcDir} not found`)); process.exit(1); }
|
|
523
|
+
const srcDir=join(PROJECTS_DIR,project==='SIMS'?'SIMS-master':'CRPMS-main');
|
|
524
|
+
if(!existsSync(srcDir)){log(chalk.red(' Error: source not found'));process.exit(1);}
|
|
638
525
|
|
|
526
|
+
// ── INSTALL ──
|
|
639
527
|
log(chalk.bold(`\n Installing ${project}...`));
|
|
640
|
-
const spinChars
|
|
641
|
-
let si
|
|
642
|
-
const spin
|
|
643
|
-
|
|
644
|
-
try
|
|
645
|
-
const entries
|
|
646
|
-
for
|
|
647
|
-
if
|
|
648
|
-
const s
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
else copyFileSync(s, d);
|
|
528
|
+
const spinChars=['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
529
|
+
let si=0;
|
|
530
|
+
const spin=setInterval(()=>{process.stdout.write(`\r ${chalk.cyan(spinChars[si++%spinChars.length])} Installing...`);},80);
|
|
531
|
+
|
|
532
|
+
try{
|
|
533
|
+
const entries=readdirSync(srcDir);
|
|
534
|
+
for(const entry of entries){
|
|
535
|
+
if(entry==='node_modules')continue;
|
|
536
|
+
const s=join(srcDir,entry),d=join(targetDir,entry);
|
|
537
|
+
if(statSync(s).isDirectory())fs.copySync(s,d,{filter:f=>!f.includes('node_modules')});
|
|
538
|
+
else copyFileSync(s,d);
|
|
652
539
|
}
|
|
653
540
|
|
|
654
|
-
const beDir
|
|
655
|
-
const feDir = join(targetDir, 'frontend-project');
|
|
541
|
+
const beDir=join(targetDir,'backend-project'),feDir=join(targetDir,'frontend-project');
|
|
656
542
|
|
|
657
|
-
if
|
|
543
|
+
if(existsSync(beDir)&&existsSync(join(beDir,'package.json'))){
|
|
658
544
|
process.chdir(beDir);
|
|
659
545
|
log(chalk.cyan('\n Installing Backend dependencies...'));
|
|
660
|
-
try
|
|
546
|
+
try{await execAsync('npm install',{timeout:120000,windowsHide:true});log(chalk.green(' ✓ Backend dependencies installed'));}catch(e){log(chalk.yellow(` ⚠ Backend: ${e.message}`));}
|
|
661
547
|
}
|
|
662
|
-
if
|
|
548
|
+
if(existsSync(feDir)&&existsSync(join(feDir,'package.json'))){
|
|
663
549
|
process.chdir(feDir);
|
|
664
550
|
log(chalk.cyan(' Installing Frontend dependencies...'));
|
|
665
|
-
try
|
|
551
|
+
try{await execAsync('npm install',{timeout:120000,windowsHide:true});log(chalk.green(' ✓ Frontend dependencies installed'));}catch(e){log(chalk.yellow(` ⚠ Frontend: ${e.message}`));}
|
|
666
552
|
}
|
|
667
553
|
|
|
668
554
|
process.chdir(targetDir);
|
|
669
555
|
clearInterval(spin);
|
|
670
556
|
process.stdout.write('\r \r');
|
|
671
|
-
log(chalk.green(' ✓ Project installed\n'));
|
|
557
|
+
log(chalk.green(' ✓ Project files installed\n'));
|
|
672
558
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
559
|
+
// Create .env
|
|
560
|
+
if(existsSync(join(beDir,'.env.example'))&&!existsSync(join(beDir,'.env'))){
|
|
561
|
+
copyFileSync(join(beDir,'.env.example'),join(beDir,'.env'));
|
|
562
|
+
log(chalk.green(' ✓ .env file created'));
|
|
676
563
|
}
|
|
677
564
|
|
|
678
|
-
|
|
679
|
-
log(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
log(` ${
|
|
686
|
-
|
|
687
|
-
const { doAssess } = await inquirer.prompt([{
|
|
688
|
-
type: 'confirm', name: 'doAssess',
|
|
689
|
-
message: 'Run auto-assessment checklist now?', default: true
|
|
690
|
-
}]);
|
|
691
|
-
|
|
692
|
-
if (doAssess) {
|
|
693
|
-
const checks = buildChecklist(targetDir, project);
|
|
694
|
-
const results = checks.map(c => {
|
|
695
|
-
let status;
|
|
696
|
-
try {
|
|
697
|
-
const result = c.auto();
|
|
698
|
-
status = result === true || result === 'PASS' ? 'PASS' : (result === 'MANUAL' ? 'MANUAL' : 'FAIL');
|
|
699
|
-
} catch { status = 'MANUAL'; }
|
|
700
|
-
return { ...c, status, guide: c.guide || '' };
|
|
701
|
-
});
|
|
702
|
-
showResults(results);
|
|
703
|
-
const reportDir = join(targetDir, 'checklist_report');
|
|
704
|
-
mkdirSync(reportDir, { recursive: true });
|
|
705
|
-
const reportFile = join(reportDir, `assessment_${project}_${Date.now()}.json`);
|
|
706
|
-
writeFileSync(reportFile, JSON.stringify({
|
|
707
|
-
project, date: new Date().toISOString(),
|
|
708
|
-
total: results.length,
|
|
709
|
-
passed: results.filter(r => r.status === 'PASS').length,
|
|
710
|
-
failed: results.filter(r => r.status === 'FAIL').length,
|
|
711
|
-
manual: results.filter(r => r.status === 'MANUAL').length,
|
|
712
|
-
results
|
|
713
|
-
}, null, 2));
|
|
714
|
-
log(chalk.dim(` Report saved: ${reportFile}\n`));
|
|
715
|
-
}
|
|
565
|
+
// ── AUTO-ASSESS ──
|
|
566
|
+
log(chalk.bold(' Running auto-assessment...\n'));
|
|
567
|
+
const checks=buildChecklist(targetDir,project);
|
|
568
|
+
const results=checks.map(c=>{let s;try{const r=c.auto();s=r===true||r==='PASS'?'PASS':(r==='MANUAL'?'MANUAL':'FAIL');}catch{s='MANUAL';}return{...c,status:s,guide:c.guide||''};});
|
|
569
|
+
showResults(results);
|
|
570
|
+
const rd=join(targetDir,'checklist_report');mkdirSync(rd,{recursive:true});
|
|
571
|
+
writeFileSync(join(rd,`assessment_${project}_${Date.now()}.json`),JSON.stringify({project,date:new Date().toISOString(),results},null,2));
|
|
572
|
+
log(chalk.dim(` Report: ${join(rd)}\n`));
|
|
716
573
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
574
|
+
// ── MERMAID DIAGRAMS ──
|
|
575
|
+
const diagPath=join(targetDir,`${project}_ERD_DFD.html`);
|
|
576
|
+
initMermaidFile(diagPath,project);
|
|
577
|
+
log(chalk.green(` ✓ Real ERD & DFD diagrams saved to:`));
|
|
578
|
+
log(` ${chalk.bold(diagPath)}`);
|
|
579
|
+
log(chalk.dim(' Open the .html file in a browser to see actual diagrams\n'));
|
|
580
|
+
|
|
581
|
+
// ── NEXT STEPS ──
|
|
582
|
+
showNextSteps(project, targetDir);
|
|
726
583
|
|
|
727
|
-
log(chalk.bold.green('\n ✓ All done
|
|
584
|
+
log(chalk.bold.green('\n ✓ All done! Follow the steps above to run your project.\n'));
|
|
728
585
|
|
|
729
|
-
}
|
|
586
|
+
}catch(err){
|
|
730
587
|
clearInterval(spin);
|
|
731
588
|
log(chalk.red(`\n ✗ Error: ${err.message}`));
|
|
732
589
|
process.exit(1);
|