npms-exam-kit 1.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/bin/exam-kit.js +357 -0
- package/package.json +25 -0
- package/projects/CRPMS-main/backend-project/config/db.js +12 -0
- package/projects/CRPMS-main/backend-project/config/initDb.js +92 -0
- package/projects/CRPMS-main/backend-project/middleware/auth.js +8 -0
- package/projects/CRPMS-main/backend-project/package-lock.json +1429 -0
- package/projects/CRPMS-main/backend-project/package.json +21 -0
- package/projects/CRPMS-main/backend-project/routes/auth.js +26 -0
- package/projects/CRPMS-main/backend-project/routes/cars.js +36 -0
- package/projects/CRPMS-main/backend-project/routes/payments.js +69 -0
- package/projects/CRPMS-main/backend-project/routes/reports.js +58 -0
- package/projects/CRPMS-main/backend-project/routes/serviceRecords.js +91 -0
- package/projects/CRPMS-main/backend-project/routes/services.js +36 -0
- package/projects/CRPMS-main/backend-project/server.js +44 -0
- package/projects/CRPMS-main/database.sql +59 -0
- package/projects/CRPMS-main/frontend-project/README.md +16 -0
- package/projects/CRPMS-main/frontend-project/eslint.config.js +21 -0
- package/projects/CRPMS-main/frontend-project/index.html +13 -0
- package/projects/CRPMS-main/frontend-project/package-lock.json +3356 -0
- package/projects/CRPMS-main/frontend-project/package.json +32 -0
- package/projects/CRPMS-main/frontend-project/public/favicon.svg +1 -0
- package/projects/CRPMS-main/frontend-project/public/icons.svg +24 -0
- package/projects/CRPMS-main/frontend-project/src/App.css +184 -0
- package/projects/CRPMS-main/frontend-project/src/App.jsx +72 -0
- package/projects/CRPMS-main/frontend-project/src/api/axios.js +8 -0
- package/projects/CRPMS-main/frontend-project/src/assets/hero.png +0 -0
- package/projects/CRPMS-main/frontend-project/src/assets/react.svg +1 -0
- package/projects/CRPMS-main/frontend-project/src/assets/vite.svg +1 -0
- package/projects/CRPMS-main/frontend-project/src/components/Navbar.jsx +54 -0
- package/projects/CRPMS-main/frontend-project/src/components/ProtectedRoute.jsx +9 -0
- package/projects/CRPMS-main/frontend-project/src/context/AuthContext.jsx +35 -0
- package/projects/CRPMS-main/frontend-project/src/index.css +14 -0
- package/projects/CRPMS-main/frontend-project/src/main.jsx +10 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Bill.jsx +227 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Cars.jsx +112 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Login.jsx +78 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Payments.jsx +153 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Reports.jsx +199 -0
- package/projects/CRPMS-main/frontend-project/src/pages/ServiceRecords.jsx +182 -0
- package/projects/CRPMS-main/frontend-project/src/pages/Services.jsx +125 -0
- package/projects/CRPMS-main/frontend-project/vite.config.js +10 -0
- package/projects/SIMS-master/backend-project/.env.example +6 -0
- package/projects/SIMS-master/backend-project/config/db.js +12 -0
- package/projects/SIMS-master/backend-project/middleware/auth.js +8 -0
- package/projects/SIMS-master/backend-project/package-lock.json +1221 -0
- package/projects/SIMS-master/backend-project/package.json +23 -0
- package/projects/SIMS-master/backend-project/routes/auth.js +29 -0
- package/projects/SIMS-master/backend-project/routes/reports.js +51 -0
- package/projects/SIMS-master/backend-project/routes/spareParts.js +34 -0
- package/projects/SIMS-master/backend-project/routes/stockIn.js +53 -0
- package/projects/SIMS-master/backend-project/routes/stockOut.js +146 -0
- package/projects/SIMS-master/backend-project/seed.js +20 -0
- package/projects/SIMS-master/backend-project/server.js +43 -0
- package/projects/SIMS-master/database.sql +43 -0
- package/projects/SIMS-master/frontend-project/index.html +12 -0
- package/projects/SIMS-master/frontend-project/package-lock.json +3352 -0
- package/projects/SIMS-master/frontend-project/package.json +27 -0
- package/projects/SIMS-master/frontend-project/postcss.config.js +6 -0
- package/projects/SIMS-master/frontend-project/src/App.jsx +53 -0
- package/projects/SIMS-master/frontend-project/src/components/Navbar.jsx +103 -0
- package/projects/SIMS-master/frontend-project/src/index.css +3 -0
- package/projects/SIMS-master/frontend-project/src/main.jsx +10 -0
- package/projects/SIMS-master/frontend-project/src/pages/Login.jsx +92 -0
- package/projects/SIMS-master/frontend-project/src/pages/Reports.jsx +279 -0
- package/projects/SIMS-master/frontend-project/src/pages/SparePart.jsx +185 -0
- package/projects/SIMS-master/frontend-project/src/pages/StockIn.jsx +170 -0
- package/projects/SIMS-master/frontend-project/src/pages/StockOut.jsx +288 -0
- package/projects/SIMS-master/frontend-project/tailwind.config.js +11 -0
- package/projects/SIMS-master/frontend-project/vite.config.js +9 -0
package/bin/exam-kit.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { execSync, exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const ROOT = join(__dirname, '..');
|
|
15
|
+
const PROJECTS_DIR = join(ROOT, 'projects');
|
|
16
|
+
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
|
|
19
|
+
function log(msg) { console.log(msg); }
|
|
20
|
+
|
|
21
|
+
const CHECKLIST = [
|
|
22
|
+
{ id: 'C1', cat: 'Preliminary (15%)', label: 'ERD Spare_Part entity drawn', auto: false },
|
|
23
|
+
{ id: 'C2', cat: 'Preliminary (15%)', label: 'ERD Stock_In entity drawn', auto: false },
|
|
24
|
+
{ id: 'C3', cat: 'Preliminary (15%)', label: 'ERD Stock_Out entity drawn', auto: false },
|
|
25
|
+
{ id: 'C4', cat: 'Preliminary (15%)', label: 'ERD entity symbol used', auto: false },
|
|
26
|
+
{ id: 'C5', cat: 'Preliminary (15%)', label: 'ERD relationship symbol used', auto: false },
|
|
27
|
+
{ id: 'C6', cat: 'Preliminary (15%)', label: 'ERD link symbol used', auto: false },
|
|
28
|
+
{ id: 'C7', cat: 'Preliminary (15%)', label: 'ERD Primary Key rule respected', auto: false },
|
|
29
|
+
{ id: 'C8', cat: 'Preliminary (15%)', label: 'ERD Foreign Key rule respected', auto: false },
|
|
30
|
+
{ id: 'C9', cat: 'Preliminary (15%)', label: 'ERD cardinalities indicated', auto: false },
|
|
31
|
+
{ id: 'C10', cat: 'Preliminary (15%)', label: 'Spare_Part-Stock_In relationship drawn', auto: false },
|
|
32
|
+
{ id: 'C11', cat: 'Preliminary (15%)', label: 'Users-Stock_Out relationship drawn', auto: false },
|
|
33
|
+
{ id: 'C12', cat: 'Preliminary (15%)', label: 'Spare_Part-Stock_Out relationship drawn', auto: false },
|
|
34
|
+
{ id: 'C13', cat: 'Preliminary (15%)', label: 'Users PK indicated in ERD', auto: false },
|
|
35
|
+
{ id: 'C14', cat: 'Preliminary (15%)', label: 'Spare_Part PK indicated in ERD', auto: false },
|
|
36
|
+
{ id: 'C15', cat: 'Preliminary (15%)', label: 'Stock_In PK indicated in ERD', auto: false },
|
|
37
|
+
{ id: 'C16', cat: 'Preliminary (15%)', label: 'Stock_Out PK indicated in ERD', auto: false },
|
|
38
|
+
{ id: 'C17', cat: 'Preliminary (15%)', label: 'Users FK in Stock_Out indicated', auto: false },
|
|
39
|
+
{ id: 'C18', cat: 'Preliminary (15%)', label: 'Spare_Part FK in Stock_In indicated', auto: false },
|
|
40
|
+
{ id: 'C19', cat: 'Preliminary (15%)', label: 'Spare_Part FK in Stock_Out indicated', auto: false },
|
|
41
|
+
{ id: 'C20', cat: 'Process (50%)', label: 'Project folder named correctly (FirstName_LastName_Exam)', auto: false },
|
|
42
|
+
{ id: 'C21', cat: 'Process (50%)', label: 'Node.js project created (package.json exists)', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'package.json')) },
|
|
43
|
+
{ id: 'C22', cat: 'Process (50%)', label: 'Express.js installed', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'express')) },
|
|
44
|
+
{ id: 'C23', cat: 'Process (50%)', label: 'Cors installed', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'cors')) },
|
|
45
|
+
{ id: 'C24', cat: 'Process (50%)', label: 'Nodemon installed', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'nodemon')) },
|
|
46
|
+
{ id: 'C25', cat: 'Process (50%)', label: 'MySQL2 installed in Node.js', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'mysql2')) },
|
|
47
|
+
{ id: 'C26', cat: 'Process (50%)', label: 'React project created', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'package.json')) },
|
|
48
|
+
{ id: 'C27', cat: 'Process (50%)', label: 'React-router-dom installed', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'node_modules', 'react-router-dom')) },
|
|
49
|
+
{ id: 'C28', cat: 'Process (50%)', label: 'Axios installed', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'node_modules', 'axios')) },
|
|
50
|
+
{ id: 'C29', cat: 'Process (50%)', label: 'SIMS/CRPMS database created', auto: true, check: () => { try { execSync('mysql -u root -e "SHOW DATABASES;" 2>nul', { stdio: 'pipe', encoding: 'utf8' }); return true; } catch { return false; } } },
|
|
51
|
+
{ id: 'C30', cat: 'Process (50%)', label: 'Users table created', auto: false },
|
|
52
|
+
{ id: 'C31', cat: 'Process (50%)', label: 'Spare_Part/Services table created', auto: false },
|
|
53
|
+
{ id: 'C32', cat: 'Process (50%)', label: 'Stock_Out/ServiceRecord table created', auto: false },
|
|
54
|
+
{ id: 'C33', cat: 'Process (50%)', label: 'Stock_In/Payment table created', auto: false },
|
|
55
|
+
{ id: 'C34', cat: 'Process (50%)', label: 'PKs applied in all tables', auto: true, check: () => existsSync(join(process.cwd(), 'database.sql')) },
|
|
56
|
+
{ id: 'C35', cat: 'Process (50%)', label: 'FKs applied in Stock_In/ServiceRecord', auto: true, check: () => existsSync(join(process.cwd(), 'database.sql')) },
|
|
57
|
+
{ id: 'C36', cat: 'Process (50%)', label: 'FKs applied in Stock_Out/Payment', auto: true, check: () => existsSync(join(process.cwd(), 'database.sql')) },
|
|
58
|
+
{ id: 'C37', cat: 'Process (50%)', label: 'React function component declared', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'src', 'App.jsx')) },
|
|
59
|
+
{ id: 'C38', cat: 'Process (50%)', label: 'React return method included', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'src', 'App.jsx')) },
|
|
60
|
+
{ id: 'C39', cat: 'Process (50%)', label: 'React function exported', auto: true, check: () => { const c = join(process.cwd(), 'frontend-project', 'src', 'App.jsx'); return existsSync(c) && readFileSync(c,'utf8').includes('export'); } },
|
|
61
|
+
{ id: 'C40', cat: 'Process (50%)', label: 'Component mounted to DOM', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'src', 'main.jsx')) },
|
|
62
|
+
{ id: 'C41', cat: 'Process (50%)', label: 'JSX used inside return method', auto: true, check: () => { const c = join(process.cwd(), 'frontend-project', 'src', 'App.jsx'); return existsSync(c) && readFileSync(c,'utf8').includes('return'); } },
|
|
63
|
+
{ id: 'C42', cat: 'Process (50%)', label: 'Login form created', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'src', 'pages')) },
|
|
64
|
+
{ id: 'C43', cat: 'Process (50%)', label: 'Spare_Part/Services form created', auto: false },
|
|
65
|
+
{ id: 'C44', cat: 'Process (50%)', label: 'Stock_In/ServiceRecord form created', auto: false },
|
|
66
|
+
{ id: 'C45', cat: 'Process (50%)', label: 'Stock_Out/Payment form created', auto: false },
|
|
67
|
+
{ id: 'C46', cat: 'Process (50%)', label: 'Routes configured', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'node_modules', 'react-router-dom')) },
|
|
68
|
+
{ id: 'C47', cat: 'Process (50%)', label: 'Links created between components', auto: false },
|
|
69
|
+
{ id: 'C48', cat: 'Process (50%)', label: 'Navigation layout created', auto: true, check: () => { try { const p = join(process.cwd(), 'frontend-project', 'src', 'components'); return existsSync(p) && readdirSync(p).length > 0; } catch { return false; } } },
|
|
70
|
+
{ id: 'C49', cat: 'Process (50%)', label: 'Tailwind CSS installed', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'node_modules', 'tailwindcss')) },
|
|
71
|
+
{ id: 'C50', cat: 'Process (50%)', label: 'Tailwind CSS configured', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'tailwind.config.js')) || existsSync(join(process.cwd(), 'frontend-project', 'vite.config.js')) },
|
|
72
|
+
{ id: 'C51', cat: 'Process (50%)', label: 'Server JS file created', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'server.js')) },
|
|
73
|
+
{ id: 'C52', cat: 'Process (50%)', label: 'Express imported', auto: true, check: () => { const s = join(process.cwd(), 'backend-project', 'server.js'); return existsSync(s) && readFileSync(s,'utf8').includes('express'); } },
|
|
74
|
+
{ id: 'C53', cat: 'Process (50%)', label: 'Cors imported', auto: true, check: () => { const s = join(process.cwd(), 'backend-project', 'server.js'); return existsSync(s) && readFileSync(s,'utf8').includes('cors'); } },
|
|
75
|
+
{ id: 'C54', cat: 'Process (50%)', label: 'Port number identified', auto: true, check: () => { const s = join(process.cwd(), 'backend-project', 'server.js'); return existsSync(s) && readFileSync(s,'utf8').includes('PORT'); } },
|
|
76
|
+
{ id: 'C55', cat: 'Process (50%)', label: 'Express object created', auto: true, check: () => { const s = join(process.cwd(), 'backend-project', 'server.js'); return existsSync(s) && readFileSync(s,'utf8').includes('express()'); } },
|
|
77
|
+
{ id: 'C56', cat: 'Process (50%)', label: 'Listen method applied', auto: true, check: () => { const s = join(process.cwd(), 'backend-project', 'server.js'); return existsSync(s) && readFileSync(s,'utf8').includes('listen'); } },
|
|
78
|
+
{ id: 'C57', cat: 'Process (50%)', label: 'MySQL2 package used', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'mysql2')) },
|
|
79
|
+
{ id: 'C58', cat: 'Process (50%)', label: 'POST endpoint created', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'routes')) },
|
|
80
|
+
{ id: 'C59', cat: 'Process (50%)', label: 'GET endpoint created', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'routes')) },
|
|
81
|
+
{ id: 'C60', cat: 'Process (50%)', label: 'PUT endpoint created', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'routes')) },
|
|
82
|
+
{ id: 'C61', cat: 'Process (50%)', label: 'DELETE endpoint created', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'routes')) },
|
|
83
|
+
{ id: 'C62', cat: 'Process (50%)', label: 'Axios imported in frontend', auto: true, check: () => existsSync(join(process.cwd(), 'frontend-project', 'node_modules', 'axios')) },
|
|
84
|
+
{ id: 'C63', cat: 'Process (50%)', label: 'Password input encrypted (bcryptjs)', auto: true, check: () => existsSync(join(process.cwd(), 'backend-project', 'node_modules', 'bcryptjs')) },
|
|
85
|
+
{ id: 'C64', cat: 'Product (30%)', label: 'Project is presented', auto: false },
|
|
86
|
+
{ id: 'C65', cat: 'Product (30%)', label: 'Input validation prevents invalid data', auto: false },
|
|
87
|
+
{ id: 'C66', cat: 'Product (30%)', label: 'Form submission saves data', auto: false },
|
|
88
|
+
{ id: 'C67', cat: 'Product (30%)', label: 'Data retrieval displays records', auto: false },
|
|
89
|
+
{ id: 'C68', cat: 'Product (30%)', label: 'Update functionality works', auto: false },
|
|
90
|
+
{ id: 'C69', cat: 'Product (30%)', label: 'Delete functionality works', auto: false },
|
|
91
|
+
{ id: 'C70', cat: 'Product (30%)', label: 'Navigation menus interactive', auto: false },
|
|
92
|
+
{ id: 'C71', cat: 'Product (30%)', label: 'Daily StockOut report generated', auto: false },
|
|
93
|
+
{ id: 'C72', cat: 'Product (30%)', label: 'Daily Stock Status report generated', auto: false },
|
|
94
|
+
{ id: 'C73', cat: 'Closing (5%)', label: 'Project folder removed after marking', auto: false },
|
|
95
|
+
{ id: 'C74', cat: 'Closing (5%)', label: 'Database deleted after marking', auto: false },
|
|
96
|
+
{ id: 'C75', cat: 'Closing (5%)', label: 'Global dependencies removed', auto: false },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const PROJECT_CHOICES = [
|
|
100
|
+
{
|
|
101
|
+
name: 'SIMS',
|
|
102
|
+
description: 'Stock Inventory Management System (SmartPark)',
|
|
103
|
+
dir: 'SIMS-master',
|
|
104
|
+
db: 'SIMS',
|
|
105
|
+
defaultUser: 'admin / admin123'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'CRPMS',
|
|
109
|
+
description: 'Car Repair Payment Management System',
|
|
110
|
+
dir: 'CRPMS-main',
|
|
111
|
+
db: 'CRPMS',
|
|
112
|
+
defaultUser: 'admin / Admin@1234'
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
async function copyProjectFiles(srcDir, destDir) {
|
|
117
|
+
const entries = readdirSync(srcDir);
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
if (entry === 'node_modules') continue;
|
|
120
|
+
const srcPath = join(srcDir, entry);
|
|
121
|
+
const destPath = join(destDir, entry);
|
|
122
|
+
const stat = statSync(srcPath);
|
|
123
|
+
if (stat.isDirectory()) {
|
|
124
|
+
fs.copySync(srcPath, destPath, { filter: (f) => !f.includes('node_modules') });
|
|
125
|
+
} else {
|
|
126
|
+
copyFileSync(srcPath, destPath);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runInstall(dir, label) {
|
|
132
|
+
log(chalk.cyan(`\n Installing ${label} dependencies...`));
|
|
133
|
+
process.chdir(dir);
|
|
134
|
+
try {
|
|
135
|
+
const { stdout, stderr } = await execAsync('npm install', { timeout: 120000, windowsHide: true });
|
|
136
|
+
if (stdout) log(stdout);
|
|
137
|
+
if (stderr) log(chalk.yellow(stderr));
|
|
138
|
+
log(chalk.green(` ✓ ${label} dependencies installed`));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
log(chalk.red(` ✗ npm install failed for ${label}: ${err.message}`));
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function runChecklist(projectType) {
|
|
146
|
+
log(chalk.bold.hex('#FFA500')('\n══════════════════════════════════════════════'));
|
|
147
|
+
log(chalk.bold.hex('#FFA500')(' NESA NATIONAL PRACTICAL EXAM CHECKLIST '));
|
|
148
|
+
log(chalk.bold.hex('#FFA500')('══════════════════════════════════════════════\n'));
|
|
149
|
+
|
|
150
|
+
log(chalk.bold(` Project: ${projectType}`));
|
|
151
|
+
log(chalk.dim(` ${new Date().toLocaleDateString()}\n`));
|
|
152
|
+
|
|
153
|
+
const results = [];
|
|
154
|
+
let currentCat = '';
|
|
155
|
+
|
|
156
|
+
for (const item of CHECKLIST) {
|
|
157
|
+
if (item.cat !== currentCat) {
|
|
158
|
+
currentCat = item.cat;
|
|
159
|
+
log(chalk.bold(chalk.bgBlue(`\n ${item.cat}`)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (item.auto && item.check) {
|
|
163
|
+
const passed = item.check();
|
|
164
|
+
const mark = passed ? chalk.green('✓') : chalk.red('✗');
|
|
165
|
+
const status = passed ? chalk.green('PASS') : chalk.red('FAIL');
|
|
166
|
+
log(` ${mark} ${item.label.padEnd(55)} ${status}`);
|
|
167
|
+
results.push({ ...item, passed, reason: passed ? '' : 'Auto-check failed' });
|
|
168
|
+
} else {
|
|
169
|
+
const { confirmed } = await inquirer.prompt([{
|
|
170
|
+
type: 'confirm',
|
|
171
|
+
name: 'confirmed',
|
|
172
|
+
message: `${item.label}`,
|
|
173
|
+
default: true
|
|
174
|
+
}]);
|
|
175
|
+
const mark = confirmed ? chalk.green('✓') : chalk.red('✗');
|
|
176
|
+
const status = confirmed ? chalk.green('PASS') : chalk.red('FAIL');
|
|
177
|
+
log(` ${mark} ${item.label.padEnd(55)} ${status}`);
|
|
178
|
+
results.push({ ...item, passed: confirmed, reason: '' });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const total = results.length;
|
|
183
|
+
const passed = results.filter(r => r.passed).length;
|
|
184
|
+
const pct = ((passed / total) * 100).toFixed(1);
|
|
185
|
+
|
|
186
|
+
log(chalk.bold('\n══════════════════════════════════════════════'));
|
|
187
|
+
log(chalk.bold(` RESULTS: ${chalk.green(passed)}/${total} passed (${pct}%)`));
|
|
188
|
+
log(chalk.bold('══════════════════════════════════════════════\n'));
|
|
189
|
+
|
|
190
|
+
const reportDir = join(process.cwd(), 'checklist_report');
|
|
191
|
+
mkdirSync(reportDir, { recursive: true });
|
|
192
|
+
const reportFile = join(reportDir, `checklist_${projectType}_${Date.now()}.json`);
|
|
193
|
+
writeFileSync(reportFile, JSON.stringify({ project: projectType, date: new Date().toISOString(), total, passed, percentage: pct, results }, null, 2));
|
|
194
|
+
log(chalk.dim(` Report saved: ${reportFile}\n`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function main() {
|
|
198
|
+
console.clear();
|
|
199
|
+
log(chalk.bold.hex('#00D2FF')('\n ╔══════════════════════════════════════════════╗'));
|
|
200
|
+
log(chalk.bold.hex('#00D2FF')(' ║ NESA NATIONAL PRACTICAL EXAM 2024-2025 ║'));
|
|
201
|
+
log(chalk.bold.hex('#00D2FF')(' ║ SECTOR: ICT & Multimedia ║'));
|
|
202
|
+
log(chalk.bold.hex('#00D2FF')(' ║ TRADE: Software Development ║'));
|
|
203
|
+
log(chalk.bold.hex('#00D2FF')(' ╚══════════════════════════════════════════════╝\n'));
|
|
204
|
+
|
|
205
|
+
log(chalk.dim(' This tool will install and verify the NESA exam project(s).\n'));
|
|
206
|
+
|
|
207
|
+
const targetDir = join(process.cwd());
|
|
208
|
+
|
|
209
|
+
const exist = [];
|
|
210
|
+
if (existsSync(join(targetDir, 'backend-project'))) exist.push('backend-project');
|
|
211
|
+
if (existsSync(join(targetDir, 'frontend-project'))) exist.push('frontend-project');
|
|
212
|
+
|
|
213
|
+
let proceed = true;
|
|
214
|
+
if (exist.length > 0) {
|
|
215
|
+
log(chalk.yellow(` Warning: ${exist.join(', ')} already exists in this directory.`));
|
|
216
|
+
const { overwrite } = await inquirer.prompt([{
|
|
217
|
+
type: 'confirm',
|
|
218
|
+
name: 'overwrite',
|
|
219
|
+
message: 'Overwrite existing files?',
|
|
220
|
+
default: false
|
|
221
|
+
}]);
|
|
222
|
+
if (overwrite) {
|
|
223
|
+
if (existsSync(join(targetDir, 'backend-project'))) fs.removeSync(join(targetDir, 'backend-project'));
|
|
224
|
+
if (existsSync(join(targetDir, 'frontend-project'))) fs.removeSync(join(targetDir, 'frontend-project'));
|
|
225
|
+
const sqlFiles = ['database.sql'];
|
|
226
|
+
for (const f of sqlFiles) { const p = join(targetDir, f); if (existsSync(p)) fs.removeSync(p); }
|
|
227
|
+
} else {
|
|
228
|
+
proceed = false;
|
|
229
|
+
log(chalk.yellow(' Operation cancelled.'));
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
log(chalk.bold('\n Available Projects:\n'));
|
|
235
|
+
for (const p of PROJECT_CHOICES) {
|
|
236
|
+
log(` ${chalk.cyan(p.name)} ${chalk.dim('-')} ${p.description}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { project } = await inquirer.prompt([{
|
|
240
|
+
type: 'list',
|
|
241
|
+
name: 'project',
|
|
242
|
+
message: 'Select project to install:',
|
|
243
|
+
choices: PROJECT_CHOICES.map(p => ({ name: `${p.name} - ${p.description}`, value: p }))
|
|
244
|
+
}]);
|
|
245
|
+
|
|
246
|
+
log(chalk.bold(`\n Installing ${project.name}...\n`));
|
|
247
|
+
|
|
248
|
+
const srcProjectDir = join(PROJECTS_DIR, project.dir);
|
|
249
|
+
if (!existsSync(srcProjectDir)) {
|
|
250
|
+
log(chalk.red(` Error: Project source not found at ${srcProjectDir}`));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const spinnerChars = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
255
|
+
let si = 0;
|
|
256
|
+
const spin = setInterval(() => { process.stdout.write(`\r ${chalk.cyan(spinnerChars[si++ % spinnerChars.length])} Copying project files...`); }, 80);
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const entries = readdirSync(srcProjectDir);
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (entry === 'node_modules') continue;
|
|
262
|
+
const srcPath = join(srcProjectDir, entry);
|
|
263
|
+
const destPath = join(targetDir, entry);
|
|
264
|
+
if (statSync(srcPath).isDirectory()) {
|
|
265
|
+
fs.copySync(srcPath, destPath, { filter: (f) => !f.includes('node_modules') });
|
|
266
|
+
} else {
|
|
267
|
+
copyFileSync(srcPath, destPath);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
clearInterval(spin);
|
|
271
|
+
process.stdout.write('\r \r');
|
|
272
|
+
log(chalk.green(' ✓ Project files copied\n'));
|
|
273
|
+
|
|
274
|
+
const backendDir = join(targetDir, 'backend-project');
|
|
275
|
+
const frontendDir = join(targetDir, 'frontend-project');
|
|
276
|
+
|
|
277
|
+
if (existsSync(backendDir) && existsSync(join(backendDir, 'package.json'))) {
|
|
278
|
+
await runInstall(backendDir, 'Backend');
|
|
279
|
+
}
|
|
280
|
+
if (existsSync(frontendDir) && existsSync(join(frontendDir, 'package.json'))) {
|
|
281
|
+
await runInstall(frontendDir, 'Frontend');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
process.chdir(targetDir);
|
|
285
|
+
|
|
286
|
+
log(chalk.bold('\n ─── Database Setup ───\n'));
|
|
287
|
+
const dbSqlPath = join(targetDir, 'database.sql');
|
|
288
|
+
if (existsSync(dbSqlPath)) {
|
|
289
|
+
log(chalk.dim(' Database SQL file found. Import instructions:\n'));
|
|
290
|
+
log(` ${chalk.cyan(' 1) Open MySQL client (MySQL Workbench / phpMyAdmin / CLI')}`);
|
|
291
|
+
log(` ${chalk.cyan(' 2) Execute the SQL script from:')}`);
|
|
292
|
+
log(` ${chalk.dim(dbSqlPath)}`);
|
|
293
|
+
log(` ${chalk.cyan(` 3) This will create the "${project.db}" database and all tables`)}\n`);
|
|
294
|
+
|
|
295
|
+
const { doDb } = await inquirer.prompt([{
|
|
296
|
+
type: 'confirm',
|
|
297
|
+
name: 'doDb',
|
|
298
|
+
message: 'Attempt automatic database import? (requires MySQL CLI)',
|
|
299
|
+
default: false
|
|
300
|
+
}]);
|
|
301
|
+
|
|
302
|
+
if (doDb) {
|
|
303
|
+
const dbSql = readFileSync(dbSqlPath, 'utf8');
|
|
304
|
+
try {
|
|
305
|
+
await execAsync('mysql -u root', { input: dbSql, timeout: 30000, windowsHide: true });
|
|
306
|
+
log(chalk.green(' ✓ Database imported successfully'));
|
|
307
|
+
} catch (err) {
|
|
308
|
+
log(chalk.yellow(` ⚠ Could not auto-import: ${err.message}`));
|
|
309
|
+
log(chalk.dim(' Please import manually using the SQL file.'));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (existsSync(join(backendDir, '.env.example'))) {
|
|
315
|
+
const envDest = join(backendDir, '.env');
|
|
316
|
+
if (!existsSync(envDest)) {
|
|
317
|
+
copyFileSync(join(backendDir, '.env.example'), envDest);
|
|
318
|
+
log(chalk.green(' ✓ .env file created from .env.example'));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
log(chalk.bold('\n══════════════════════════════════════════════'));
|
|
323
|
+
log(chalk.bold(' INSTALLATION SUMMARY'));
|
|
324
|
+
log(chalk.bold('══════════════════════════════════════════════\n'));
|
|
325
|
+
log(` ${chalk.cyan('Project:')} ${project.name}`);
|
|
326
|
+
log(` ${chalk.cyan('Database:')} ${project.db}`);
|
|
327
|
+
log(` ${chalk.cyan('Default User:')} ${project.defaultUser}`);
|
|
328
|
+
log(` ${chalk.cyan('Backend:')} cd backend-project && npm start (port 5000)`);
|
|
329
|
+
log(` ${chalk.cyan('Frontend:')} cd frontend-project && npm run dev (port 5173)`);
|
|
330
|
+
log(chalk.dim(`\n ${targetDir}\n`));
|
|
331
|
+
|
|
332
|
+
const { runCheck } = await inquirer.prompt([{
|
|
333
|
+
type: 'confirm',
|
|
334
|
+
name: 'runCheck',
|
|
335
|
+
message: 'Run the NESA assessment checklist now?',
|
|
336
|
+
default: true
|
|
337
|
+
}]);
|
|
338
|
+
|
|
339
|
+
if (runCheck) {
|
|
340
|
+
await runChecklist(project.name);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
log(chalk.bold.green('\n ✓ Installation complete!'));
|
|
344
|
+
log(`\n ${chalk.dim('To start the backend:')}`);
|
|
345
|
+
log(` ${chalk.cyan(' cd backend-project && npm start')}`);
|
|
346
|
+
log(`\n ${chalk.dim('To start the frontend (new terminal):')}`);
|
|
347
|
+
log(` ${chalk.cyan(' cd frontend-project && npm run dev')}`);
|
|
348
|
+
log(`\n ${chalk.dim('Open:')} ${chalk.cyan('http://localhost:5173')}\n`);
|
|
349
|
+
|
|
350
|
+
} catch (err) {
|
|
351
|
+
clearInterval(spin);
|
|
352
|
+
log(chalk.red(`\n ✗ Error: ${err.message}`));
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "npms-exam-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "NESA National Practical Exam Projects Installer - SIMS & CRPMS",
|
|
5
|
+
"bin": {
|
|
6
|
+
"exam-kit": "./bin/exam-kit.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": ["nesa", "exam", "sims", "crpms", "stock-inventory", "car-repair"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"chalk": "^5.3.0",
|
|
12
|
+
"inquirer": "^9.2.0",
|
|
13
|
+
"ora": "^8.0.0",
|
|
14
|
+
"fs-extra": "^11.2.0"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.0.0"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin/",
|
|
22
|
+
"src/",
|
|
23
|
+
"projects/"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const mysql = require('mysql2/promise');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
|
|
4
|
+
async function initDb() {
|
|
5
|
+
const conn = await mysql.createConnection({
|
|
6
|
+
host: 'localhost',
|
|
7
|
+
user: 'root',
|
|
8
|
+
password: '',
|
|
9
|
+
multipleStatements: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
console.log('Connected to MySQL. Initializing CRPMS database...');
|
|
13
|
+
|
|
14
|
+
await conn.query(`CREATE DATABASE IF NOT EXISTS CRPMS`);
|
|
15
|
+
await conn.query(`USE CRPMS`);
|
|
16
|
+
|
|
17
|
+
await conn.query(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS Services (
|
|
19
|
+
ServiceCode VARCHAR(10) PRIMARY KEY,
|
|
20
|
+
ServiceName VARCHAR(100) NOT NULL,
|
|
21
|
+
ServicePrice DECIMAL(10,2) NOT NULL
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE IF NOT EXISTS Car (
|
|
25
|
+
PlateNumber VARCHAR(20) PRIMARY KEY,
|
|
26
|
+
type VARCHAR(50) NOT NULL,
|
|
27
|
+
Model VARCHAR(100) NOT NULL,
|
|
28
|
+
ManufacturingYear INT NOT NULL,
|
|
29
|
+
DriverPhone VARCHAR(15) NOT NULL,
|
|
30
|
+
MechanicName VARCHAR(100) NOT NULL
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS User (
|
|
34
|
+
UserID INT AUTO_INCREMENT PRIMARY KEY,
|
|
35
|
+
Username VARCHAR(50) UNIQUE NOT NULL,
|
|
36
|
+
Password VARCHAR(255) NOT NULL,
|
|
37
|
+
FullName VARCHAR(100) NOT NULL
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS ServiceRecord (
|
|
41
|
+
RecordNumber INT AUTO_INCREMENT PRIMARY KEY,
|
|
42
|
+
ServiceDate DATE NOT NULL,
|
|
43
|
+
PlateNumber VARCHAR(20),
|
|
44
|
+
ServiceCode VARCHAR(10),
|
|
45
|
+
FOREIGN KEY (PlateNumber) REFERENCES Car(PlateNumber) ON DELETE SET NULL,
|
|
46
|
+
FOREIGN KEY (ServiceCode) REFERENCES Services(ServiceCode) ON DELETE SET NULL
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS Payment (
|
|
50
|
+
PaymentNumber INT AUTO_INCREMENT PRIMARY KEY,
|
|
51
|
+
AmountPaid DECIMAL(10,2) NOT NULL,
|
|
52
|
+
PaymentDate DATE NOT NULL,
|
|
53
|
+
RecordNumber INT,
|
|
54
|
+
UserID INT,
|
|
55
|
+
FOREIGN KEY (RecordNumber) REFERENCES ServiceRecord(RecordNumber) ON DELETE SET NULL,
|
|
56
|
+
FOREIGN KEY (UserID) REFERENCES User(UserID) ON DELETE SET NULL
|
|
57
|
+
);
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
console.log('Tables created.');
|
|
61
|
+
|
|
62
|
+
// Seed services
|
|
63
|
+
const services = [
|
|
64
|
+
['SRV001', 'Engine Repair', 150000],
|
|
65
|
+
['SRV002', 'Transmission Repair', 80000],
|
|
66
|
+
['SRV003', 'Oil Change', 60000],
|
|
67
|
+
['SRV004', 'Chain Replacement', 40000],
|
|
68
|
+
['SRV005', 'Disc Replacement', 400000],
|
|
69
|
+
['SRV006', 'Wheel Alignment', 5000],
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const [code, name, price] of services) {
|
|
73
|
+
await conn.query(
|
|
74
|
+
`INSERT IGNORE INTO Services (ServiceCode, ServiceName, ServicePrice) VALUES (?, ?, ?)`,
|
|
75
|
+
[code, name, price]
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
console.log('Services seeded.');
|
|
79
|
+
|
|
80
|
+
// Create default admin user
|
|
81
|
+
const hashedPwd = await bcrypt.hash('Admin@1234', 12);
|
|
82
|
+
await conn.query(
|
|
83
|
+
`INSERT IGNORE INTO User (Username, Password, FullName) VALUES (?, ?, ?)`,
|
|
84
|
+
['admin', hashedPwd, 'Chief Mechanic']
|
|
85
|
+
);
|
|
86
|
+
console.log('Default user created: admin / Admin@1234');
|
|
87
|
+
|
|
88
|
+
await conn.end();
|
|
89
|
+
console.log('Database initialization complete.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
initDb().catch(console.error);
|