create-lumina-project 1.0.1 ā 1.2.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/cli.js +106 -28
- package/package.json +3 -2
- package/template/.env.example +1 -0
- package/template/package.json +61 -55
- package/template/public/js/status.js +53 -0
- package/template/scripts/create-migration.ts +457 -16
- package/template/server.ts +50 -8
- package/template/src/config/database.ts +11 -13
- package/template/src/config/env.ts +54 -0
- package/template/src/controllers/AuthController.ts +30 -0
- package/template/src/database/migrations/20260222151300-create_refresh_tokens.js +63 -0
- package/template/src/database/migrations/20260420050854-create_users.js +68 -0
- package/template/src/database/migrations/20260420050855-create_refresh_tokens.js +60 -0
- package/template/src/exceptions/Handler.ts +10 -3
- package/template/src/middlewares/Authentication.ts +2 -5
- package/template/src/middlewares/Csrf.ts +52 -0
- package/template/src/middlewares/Maintenance.ts +2 -4
- package/template/src/middlewares/RequestLogger.ts +31 -0
- package/template/src/models/RefreshToken.ts +69 -0
- package/template/src/models/index.ts +4 -6
- package/template/src/routes/api.ts +5 -0
- package/template/src/routes/web.ts +34 -5
- package/template/src/services/AuthService.ts +101 -9
- package/template/src/services/StorageService.ts +36 -9
- package/template/src/tests/apiResponse.test.ts +66 -0
- package/template/src/tests/env.test.ts +99 -0
- package/template/src/tests/hash.test.ts +30 -0
- package/template/src/utils/Logger.ts +16 -6
- package/template/views/404.html +233 -0
- package/template/views/maintenance.html +445 -23
- package/template/views/status.html +545 -0
- package/template/views/welcome.html +472 -39
- package/template/vitest.config.ts +10 -0
package/bin/cli.js
CHANGED
|
@@ -3,70 +3,148 @@
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import prompts from 'prompts';
|
|
6
|
-
import { bold, green, cyan, red } from 'kleur/colors';
|
|
6
|
+
import { bold, green, cyan, red, yellow } from 'kleur/colors';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import util from 'util';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
|
|
12
|
+
const execAsync = util.promisify(exec);
|
|
8
13
|
|
|
9
14
|
// Get current directory of the script
|
|
10
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
16
|
const __dirname = path.dirname(__filename);
|
|
12
17
|
|
|
13
18
|
async function init() {
|
|
14
|
-
console.log(bold(cyan('
|
|
19
|
+
console.log(`\n${bold(cyan('š Welcome to the Lumina Installer!'))}\n`);
|
|
15
20
|
|
|
16
21
|
// 1. Ask for Project Name
|
|
17
22
|
let targetDir = process.argv[2];
|
|
18
23
|
|
|
19
24
|
if (!targetDir) {
|
|
20
|
-
const response = await prompts(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const response = await prompts(
|
|
26
|
+
{
|
|
27
|
+
type: 'text',
|
|
28
|
+
name: 'projectName',
|
|
29
|
+
message: 'What is your project name?',
|
|
30
|
+
initial: 'my-lumina-app',
|
|
31
|
+
validate: (name) => {
|
|
32
|
+
if (!name.trim()) return 'Project name cannot be empty.';
|
|
33
|
+
if (!/^[a-z0-9-_]+$/.test(name)) {
|
|
34
|
+
return 'Project name can only contain lowercase letters, numbers, dashes, and underscores.';
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
onCancel: () => {
|
|
41
|
+
console.log(red('ā Operation cancelled'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
targetDir = response.projectName.trim();
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
const projectPath = path.
|
|
50
|
+
const projectPath = path.resolve(process.cwd(), targetDir);
|
|
35
51
|
|
|
36
52
|
// 2. Check if folder exists
|
|
37
53
|
if (fs.existsSync(projectPath)) {
|
|
38
54
|
console.log(red(`\nā Error: Directory "${targetDir}" already exists.`));
|
|
39
55
|
process.exit(1);
|
|
40
56
|
}
|
|
57
|
+
|
|
58
|
+
// 3. Prompt for extra setups (git and npm install)
|
|
59
|
+
const setupPreferences = await prompts(
|
|
60
|
+
[
|
|
61
|
+
{
|
|
62
|
+
type: 'confirm',
|
|
63
|
+
name: 'initGit',
|
|
64
|
+
message: 'Initialize a new git repository?',
|
|
65
|
+
initial: true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'installDeps',
|
|
70
|
+
message: 'Install dependencies automatically?',
|
|
71
|
+
initial: true
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
onCancel: () => {
|
|
76
|
+
console.log(red('ā Operation cancelled'));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
41
81
|
|
|
42
|
-
console.log(
|
|
82
|
+
console.log(''); // Blank space for readability
|
|
43
83
|
|
|
44
|
-
|
|
84
|
+
const copySpinner = ora(`š Creating project in ${green(projectPath)}...`).start();
|
|
85
|
+
|
|
86
|
+
// 4. Copy Template
|
|
45
87
|
const templateDir = path.resolve(__dirname, '../template');
|
|
46
88
|
|
|
47
89
|
try {
|
|
48
90
|
await fs.copy(templateDir, projectPath);
|
|
49
91
|
|
|
50
|
-
//
|
|
92
|
+
// 5. Rename _gitignore to .gitignore
|
|
51
93
|
const gitignorePath = path.join(projectPath, '_gitignore');
|
|
52
94
|
if (fs.existsSync(gitignorePath)) {
|
|
53
95
|
fs.renameSync(gitignorePath, path.join(projectPath, '.gitignore'));
|
|
54
96
|
}
|
|
55
97
|
|
|
56
|
-
//
|
|
98
|
+
// 6. Update package.json name
|
|
57
99
|
const pkgPath = path.join(projectPath, 'package.json');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
if (fs.existsSync(pkgPath)) {
|
|
101
|
+
const pkg = await fs.readJson(pkgPath);
|
|
102
|
+
pkg.name = path.basename(projectPath);
|
|
103
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
104
|
+
}
|
|
61
105
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
106
|
+
copySpinner.succeed(green('Project template copied successfully!'));
|
|
107
|
+
|
|
108
|
+
// 7. Initialize Git
|
|
109
|
+
if (setupPreferences.initGit) {
|
|
110
|
+
const gitSpinner = ora('Initializing git repository...').start();
|
|
111
|
+
try {
|
|
112
|
+
await execAsync('git init', { cwd: projectPath });
|
|
113
|
+
// Optional: add initial commit but usually people prefer to do it themselves or standard tooling just inits
|
|
114
|
+
gitSpinner.succeed(green('Git repository initialized.'));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
gitSpinner.fail(yellow('Failed to initialize git repository (is git installed?)'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 8. Install Dependencies
|
|
121
|
+
if (setupPreferences.installDeps) {
|
|
122
|
+
const depsSpinner = ora('Installing dependencies (this might take a few minutes)...').start();
|
|
123
|
+
try {
|
|
124
|
+
await execAsync('npm install', { cwd: projectPath });
|
|
125
|
+
depsSpinner.succeed(green('Dependencies installed successfully!'));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
depsSpinner.fail(red('Failed to install dependencies. You can run "npm install" manually later.'));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 9. Success message & Next steps
|
|
132
|
+
console.log(`\n${bold(green('ā
All done! Project setup complete.'))}`);
|
|
133
|
+
console.log('\nTo get started run:\n');
|
|
134
|
+
|
|
135
|
+
const cdCmd = projectPath === process.cwd() ? '' : `cd ${targetDir}`;
|
|
136
|
+
if (cdCmd) console.log(cyan(` ${cdCmd}`));
|
|
137
|
+
|
|
138
|
+
if (!setupPreferences.installDeps) {
|
|
139
|
+
console.log(cyan(' npm install'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(cyan(' npm run dev\n'));
|
|
67
143
|
|
|
68
144
|
} catch (error) {
|
|
69
|
-
|
|
145
|
+
copySpinner.fail(red('Error copying files:'));
|
|
146
|
+
console.error(error);
|
|
147
|
+
process.exit(1);
|
|
70
148
|
}
|
|
71
149
|
}
|
|
72
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lumina-project",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"author": "Glenson Ansin",
|
|
5
5
|
"description": "Scaffold a modern Node.js API with Lumina architecture.",
|
|
6
6
|
"type": "module",
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"template"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"fs-extra": "^11.3.
|
|
15
|
+
"fs-extra": "^11.3.4",
|
|
16
16
|
"kleur": "^4.1.5",
|
|
17
|
+
"ora": "^9.3.0",
|
|
17
18
|
"prompts": "^2.4.2"
|
|
18
19
|
},
|
|
19
20
|
"license": "MIT",
|
package/template/.env.example
CHANGED
package/template/package.json
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "lumina",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "server.ts",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "nodemon --exec tsx server.ts",
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/server.js",
|
|
10
|
-
"test": "
|
|
11
|
-
"migrate": "tsx scripts/migrate.ts up",
|
|
12
|
-
"migrate:
|
|
13
|
-
"migrate:
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"create:
|
|
19
|
-
"create:
|
|
20
|
-
"create:
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
"bcrypt": "^6.0.0",
|
|
28
|
-
"bcryptjs": "^3.0.3",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"@
|
|
50
|
-
"@types/
|
|
51
|
-
"@types/
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "lumina",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "server.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "nodemon --exec tsx server.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/server.js",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"migrate": "tsx scripts/migrate.ts up",
|
|
12
|
+
"migrate:seed": "tsx scripts/migrate.ts up && tsx scripts/seed.ts",
|
|
13
|
+
"migrate:undo": "tsx scripts/migrate.ts down",
|
|
14
|
+
"migrate:reset": "tsx scripts/migrate.ts reset",
|
|
15
|
+
"db:seed": "tsx scripts/seed.ts",
|
|
16
|
+
"down": "tsx scripts/maintenance.ts down",
|
|
17
|
+
"up": "tsx scripts/maintenance.ts up",
|
|
18
|
+
"create:model": "tsx scripts/create-model.ts",
|
|
19
|
+
"create:migration": "tsx scripts/create-migration.ts",
|
|
20
|
+
"create:controller": "tsx scripts/create-controller.ts",
|
|
21
|
+
"create:factory": "tsx scripts/create-factory.ts",
|
|
22
|
+
"key:generate": "tsx scripts/generate-keys.ts"
|
|
23
|
+
},
|
|
24
|
+
"private": true,
|
|
25
|
+
"type": "module",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"bcrypt": "^6.0.0",
|
|
28
|
+
"bcryptjs": "^3.0.3",
|
|
29
|
+
"compression": "^1.8.1",
|
|
30
|
+
"cookie-parser": "^1.4.7",
|
|
31
|
+
"cors": "^2.8.6",
|
|
32
|
+
"dotenv": "^17.4.2",
|
|
33
|
+
"express": "^5.2.1",
|
|
34
|
+
"express-rate-limit": "^8.3.2",
|
|
35
|
+
"helmet": "^8.1.0",
|
|
36
|
+
"jsonwebtoken": "^9.0.3",
|
|
37
|
+
"multer": "^2.1.1",
|
|
38
|
+
"mysql2": "^3.22.1",
|
|
39
|
+
"nodemon": "^3.1.14",
|
|
40
|
+
"pg": "^8.20.0",
|
|
41
|
+
"pg-hstore": "^2.3.4",
|
|
42
|
+
"sequelize": "^6.37.8",
|
|
43
|
+
"sequelize-cli": "^6.6.5",
|
|
44
|
+
"umzug": "^3.8.2",
|
|
45
|
+
"winston": "^3.19.0",
|
|
46
|
+
"zod": "^4.3.6"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@faker-js/faker": "^10.4.0",
|
|
50
|
+
"@types/bcrypt": "^6.0.0",
|
|
51
|
+
"@types/compression": "^1.8.1",
|
|
52
|
+
"@types/cookie-parser": "^1.4.10",
|
|
53
|
+
"@types/cors": "^2.8.19",
|
|
54
|
+
"@types/express": "^5.0.6",
|
|
55
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
56
|
+
"@types/multer": "^2.1.0",
|
|
57
|
+
"tsx": "^4.21.0",
|
|
58
|
+
"typescript": "^6.0.3",
|
|
59
|
+
"vitest": "^4.1.4"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
async function fetchStatus() {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetch('/status/json');
|
|
4
|
+
const data = await res.json();
|
|
5
|
+
|
|
6
|
+
// Status
|
|
7
|
+
const heroEl = document.getElementById('statusHero');
|
|
8
|
+
const heroText = document.getElementById('statusHeroText');
|
|
9
|
+
const appStatus = document.getElementById('appStatus');
|
|
10
|
+
const isUp = data.status === 'UP';
|
|
11
|
+
|
|
12
|
+
heroEl.className = 'status-hero ' + (isUp ? 'up' : 'down');
|
|
13
|
+
heroText.textContent = isUp ? 'All Systems Operational' : 'System Degraded';
|
|
14
|
+
appStatus.textContent = data.status;
|
|
15
|
+
appStatus.className = 'info-value ' + (isUp ? 'status-up' : 'status-down');
|
|
16
|
+
|
|
17
|
+
// Environment
|
|
18
|
+
document.getElementById('appEnv').textContent = data.environment || 'unknown';
|
|
19
|
+
|
|
20
|
+
// Uptime
|
|
21
|
+
if (data.uptime !== undefined) {
|
|
22
|
+
document.getElementById('appUptime').textContent = formatUptime(data.uptime);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Memory
|
|
26
|
+
if (data.memoryMB !== undefined) {
|
|
27
|
+
document.getElementById('appMemory').textContent = data.memoryMB + ' MB';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Timestamp
|
|
31
|
+
document.getElementById('lastChecked').textContent = 'Checked ' + new Date().toLocaleTimeString();
|
|
32
|
+
} catch (err) {
|
|
33
|
+
document.getElementById('statusHero').className = 'status-hero down';
|
|
34
|
+
document.getElementById('statusHeroText').textContent = 'Unable to Reach Server';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatUptime(seconds) {
|
|
39
|
+
const d = Math.floor(seconds / 86400);
|
|
40
|
+
const h = Math.floor((seconds % 86400) / 3600);
|
|
41
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
42
|
+
const s = Math.floor(seconds % 60);
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (d > 0) parts.push(d + 'd');
|
|
45
|
+
if (h > 0) parts.push(h + 'h');
|
|
46
|
+
if (m > 0) parts.push(m + 'm');
|
|
47
|
+
parts.push(s + 's');
|
|
48
|
+
return parts.join(' ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fetch immediately, then refresh every 30s
|
|
52
|
+
fetchStatus();
|
|
53
|
+
setInterval(fetchStatus, 30000);
|