create-lumina-project 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/cli.js +73 -0
- package/package.json +28 -0
- package/template/.env.example +15 -0
- package/template/.sequelizerc +11 -0
- package/template/_gitignore +3 -0
- package/template/nodemon.json +5 -0
- package/template/package.json +53 -0
- package/template/public/img/logo1.png +0 -0
- package/template/public/img/logo2.png +0 -0
- package/template/scripts/create-controller.ts +89 -0
- package/template/scripts/create-factory.ts +98 -0
- package/template/scripts/create-migration.ts +98 -0
- package/template/scripts/create-model.ts +105 -0
- package/template/scripts/maintenance.ts +73 -0
- package/template/scripts/migrate.ts +146 -0
- package/template/scripts/seed.ts +42 -0
- package/template/scripts/stubs/controller.stub +50 -0
- package/template/scripts/stubs/factory.stub +19 -0
- package/template/scripts/stubs/migration.stub +44 -0
- package/template/scripts/stubs/model.stub +39 -0
- package/template/server.ts +63 -0
- package/template/src/config/database.ts +39 -0
- package/template/src/controllers/AuthController.ts +21 -0
- package/template/src/controllers/UserController.ts +52 -0
- package/template/src/database/factories/Factory.ts +33 -0
- package/template/src/database/factories/UserFactory.ts +26 -0
- package/template/src/database/migrations/20260205084944-create_user.js +68 -0
- package/template/src/database/seeders/DatabaseSeeder.js +32 -0
- package/template/src/exceptions/Handler.ts +32 -0
- package/template/src/middlewares/Authentication.ts +34 -0
- package/template/src/middlewares/Limiter.ts +34 -0
- package/template/src/middlewares/Maintenance.ts +37 -0
- package/template/src/middlewares/Validator.ts +29 -0
- package/template/src/models/User.ts +91 -0
- package/template/src/models/index.ts +92 -0
- package/template/src/requests/UserRequest.ts +26 -0
- package/template/src/routes/api.ts +30 -0
- package/template/src/routes/web.ts +29 -0
- package/template/src/services/AuthService.ts +29 -0
- package/template/src/services/RouteService.ts +15 -0
- package/template/src/services/StorageService.ts +59 -0
- package/template/src/services/UserService.ts +31 -0
- package/template/src/types/Pagination.d.ts +13 -0
- package/template/src/types/express/index.d.ts +9 -0
- package/template/src/utils/ApiResponse.ts +27 -0
- package/template/src/utils/Hash.ts +21 -0
- package/template/src/utils/Logger.ts +79 -0
- package/template/src/utils/Paginator.ts +41 -0
- package/template/tsconfig.json +24 -0
- package/template/views/maintenance.html +42 -0
- package/template/views/welcome.html +62 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import prompts from 'prompts';
|
|
6
|
+
import { bold, green, cyan, red } from 'kleur/colors';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
// Get current directory of the script
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
async function init() {
|
|
14
|
+
console.log(bold(cyan('\nš Welcome to the Lumina Installer!')));
|
|
15
|
+
|
|
16
|
+
// 1. Ask for Project Name
|
|
17
|
+
let targetDir = process.argv[2];
|
|
18
|
+
|
|
19
|
+
if (!targetDir) {
|
|
20
|
+
const response = await prompts({
|
|
21
|
+
type: 'text',
|
|
22
|
+
name: 'projectName',
|
|
23
|
+
message: 'What is your project name?',
|
|
24
|
+
initial: 'my-lumina-app'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!response.projectName) {
|
|
28
|
+
console.log(red('ā Operation cancelled'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
targetDir = response.projectName;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const projectPath = path.join(process.cwd(), targetDir);
|
|
35
|
+
|
|
36
|
+
// 2. Check if folder exists
|
|
37
|
+
if (fs.existsSync(projectPath)) {
|
|
38
|
+
console.log(red(`\nā Error: Directory "${targetDir}" already exists.`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\nš Creating project in ${green(projectPath)}...`);
|
|
43
|
+
|
|
44
|
+
// 3. Copy Template
|
|
45
|
+
const templateDir = path.resolve(__dirname, '../template');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await fs.copy(templateDir, projectPath);
|
|
49
|
+
|
|
50
|
+
// 4. Rename _gitignore to .gitignore (npm usually strips .gitignore from published packages)
|
|
51
|
+
const gitignorePath = path.join(projectPath, '_gitignore');
|
|
52
|
+
if (fs.existsSync(gitignorePath)) {
|
|
53
|
+
fs.renameSync(gitignorePath, path.join(projectPath, '.gitignore'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 5. Update package.json name
|
|
57
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
58
|
+
const pkg = await fs.readJson(pkgPath);
|
|
59
|
+
pkg.name = targetDir;
|
|
60
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
61
|
+
|
|
62
|
+
console.log(green('\nā
Project setup complete!'));
|
|
63
|
+
console.log('\nTo get started run:');
|
|
64
|
+
console.log(cyan(` cd ${targetDir}`));
|
|
65
|
+
console.log(cyan(' npm install'));
|
|
66
|
+
console.log(cyan(' npm run dev'));
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(red('Error copying files:'), error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
init();
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-lumina-project",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "Ghost",
|
|
5
|
+
"description": "Scaffold a modern Node.js API with Lumina architecture.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-lumina-project": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"template"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"fs-extra": "^11.3.3",
|
|
16
|
+
"kleur": "^4.1.5",
|
|
17
|
+
"prompts": "^2.4.2"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/GlensonAnsin/lumina.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/GlensonAnsin/lumina/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/GlensonAnsin/lumina#readme"
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
'config': path.resolve(__dirname, 'src/config', 'database.ts'),
|
|
8
|
+
'models-path': path.resolve(__dirname, 'src/models'),
|
|
9
|
+
'seeders-path': path.resolve(__dirname, 'src/database/seeders'),
|
|
10
|
+
'migrations-path': path.resolve(__dirname, 'src/database/migrations')
|
|
11
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
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": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"migrate": "tsx scripts/migrate.ts up",
|
|
12
|
+
"migrate:undo": "tsx scripts/migrate.ts down",
|
|
13
|
+
"migrate:reset": "tsx scripts/migrate.ts reset",
|
|
14
|
+
"db:seed": "tsx scripts/seed.ts",
|
|
15
|
+
"down": "tsx scripts/maintenance.ts down",
|
|
16
|
+
"up": "tsx scripts/maintenance.ts up",
|
|
17
|
+
"create:model": "tsx scripts/create-model.ts",
|
|
18
|
+
"create:migration": "tsx scripts/create-migration.ts",
|
|
19
|
+
"create:controller": "tsx scripts/create-controller.ts",
|
|
20
|
+
"create:factory": "tsx scripts/create-factory.ts"
|
|
21
|
+
},
|
|
22
|
+
"author": "Ghost",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@faker-js/faker": "^10.2.0",
|
|
27
|
+
"bcrypt": "^6.0.0",
|
|
28
|
+
"bcryptjs": "^3.0.3",
|
|
29
|
+
"cors": "^2.8.6",
|
|
30
|
+
"dotenv": "^17.2.3",
|
|
31
|
+
"express": "^5.2.1",
|
|
32
|
+
"express-rate-limit": "^8.2.1",
|
|
33
|
+
"helmet": "^8.1.0",
|
|
34
|
+
"jsonwebtoken": "^9.0.3",
|
|
35
|
+
"multer": "^2.0.2",
|
|
36
|
+
"mysql2": "^3.16.2",
|
|
37
|
+
"nodemon": "^3.1.11",
|
|
38
|
+
"sequelize": "^6.37.7",
|
|
39
|
+
"sequelize-cli": "^6.6.5",
|
|
40
|
+
"umzug": "^3.8.2",
|
|
41
|
+
"winston": "^3.19.0",
|
|
42
|
+
"zod": "^4.3.6"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/bcrypt": "^6.0.0",
|
|
46
|
+
"@types/cors": "^2.8.19",
|
|
47
|
+
"@types/express": "^5.0.6",
|
|
48
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
49
|
+
"@types/multer": "^2.0.0",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Logger from '../src/utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
class ControllerGenerator {
|
|
6
|
+
private controllerName: string;
|
|
7
|
+
private stubPath: string;
|
|
8
|
+
private controllersDir: string;
|
|
9
|
+
private targetPath: string;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
// 1. Get Argument
|
|
13
|
+
const rawName = process.argv[2];
|
|
14
|
+
|
|
15
|
+
// 2. Validate & Format
|
|
16
|
+
if (rawName) {
|
|
17
|
+
this.controllerName = this.formatName(rawName);
|
|
18
|
+
} else {
|
|
19
|
+
this.controllerName = '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 3. Define Paths
|
|
23
|
+
this.stubPath = path.join(process.cwd(), 'scripts', 'stubs', 'controller.stub');
|
|
24
|
+
this.controllersDir = path.join(process.cwd(), 'src', 'controllers');
|
|
25
|
+
|
|
26
|
+
this.targetPath = this.controllerName
|
|
27
|
+
? path.join(this.controllersDir, `${this.controllerName}.ts`)
|
|
28
|
+
: '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Helper: Ensures strict PascalCase and appends 'Controller' if missing.
|
|
33
|
+
* "user" -> "UserController"
|
|
34
|
+
* "ProductController" -> "ProductController"
|
|
35
|
+
*/
|
|
36
|
+
private formatName(str: string): string {
|
|
37
|
+
let name = str.charAt(0).toUpperCase() + str.slice(1);
|
|
38
|
+
|
|
39
|
+
// Append 'Controller' if user forgot it
|
|
40
|
+
if (!name.endsWith('Controller')) {
|
|
41
|
+
name += 'Controller';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return name;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public run(): void {
|
|
48
|
+
if (!this.validateInput()) return;
|
|
49
|
+
|
|
50
|
+
if (this.controllerExists()) {
|
|
51
|
+
Logger.error(`Controller "${this.controllerName}" already exists at src/controllers/${this.controllerName}.ts`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.createFile();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private validateInput(): boolean {
|
|
59
|
+
if (!this.controllerName) {
|
|
60
|
+
Logger.error('Please provide a controller name.');
|
|
61
|
+
console.log('\nUsage: npm run make:controller <Name>');
|
|
62
|
+
console.log('Example: npm run make:controller ProductController\n');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private controllerExists(): boolean {
|
|
69
|
+
return fs.existsSync(this.targetPath);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private createFile(): void {
|
|
73
|
+
try {
|
|
74
|
+
let content = fs.readFileSync(this.stubPath, 'utf-8');
|
|
75
|
+
|
|
76
|
+
// Replace Placeholder
|
|
77
|
+
content = content.replace(/{{ControllerName}}/g, this.controllerName);
|
|
78
|
+
|
|
79
|
+
fs.writeFileSync(this.targetPath, content);
|
|
80
|
+
|
|
81
|
+
Logger.info(`Controller Created: src/controllers/${this.controllerName}.ts`);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
Logger.error('Failed to create controller:', error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
new ControllerGenerator().run();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Logger from '../src/utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
class FactoryGenerator {
|
|
6
|
+
private factoryName: string;
|
|
7
|
+
private modelName: string;
|
|
8
|
+
private stubPath: string;
|
|
9
|
+
private factoriesDir: string;
|
|
10
|
+
private targetPath: string;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
// 1. Get Argument
|
|
14
|
+
const rawName = process.argv[2];
|
|
15
|
+
|
|
16
|
+
// 2. Validate & Format
|
|
17
|
+
if (rawName) {
|
|
18
|
+
this.factoryName = this.formatFactoryName(rawName);
|
|
19
|
+
this.modelName = this.guessModelName(this.factoryName);
|
|
20
|
+
} else {
|
|
21
|
+
this.factoryName = '';
|
|
22
|
+
this.modelName = '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 3. Define Paths
|
|
26
|
+
this.stubPath = path.join(process.cwd(), 'scripts', 'stubs', 'factory.stub');
|
|
27
|
+
this.factoriesDir = path.join(process.cwd(), 'src', 'database', 'factories');
|
|
28
|
+
|
|
29
|
+
this.targetPath = this.factoryName
|
|
30
|
+
? path.join(this.factoriesDir, `${this.factoryName}.ts`)
|
|
31
|
+
: '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensures 'Factory' suffix and PascalCase.
|
|
36
|
+
* "product" -> "ProductFactory"
|
|
37
|
+
*/
|
|
38
|
+
private formatFactoryName(str: string): string {
|
|
39
|
+
let name = str.charAt(0).toUpperCase() + str.slice(1);
|
|
40
|
+
if (!name.endsWith('Factory')) {
|
|
41
|
+
name += 'Factory';
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Guesses model name by removing 'Factory'.
|
|
48
|
+
* "ProductFactory" -> "Product"
|
|
49
|
+
*/
|
|
50
|
+
private guessModelName(factoryName: string): string {
|
|
51
|
+
return factoryName.replace('Factory', '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public run(): void {
|
|
55
|
+
if (!this.validateInput()) return;
|
|
56
|
+
|
|
57
|
+
if (this.factoryExists()) {
|
|
58
|
+
Logger.error(`Factory "${this.factoryName}" already exists.`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.createFile();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private validateInput(): boolean {
|
|
66
|
+
if (!this.factoryName) {
|
|
67
|
+
Logger.error('Please provide a factory name.');
|
|
68
|
+
console.log('\nUsage: npm run make:factory <Name>');
|
|
69
|
+
console.log('Example: npm run make:factory Product\n');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private factoryExists(): boolean {
|
|
76
|
+
return fs.existsSync(this.targetPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private createFile(): void {
|
|
80
|
+
try {
|
|
81
|
+
let content = fs.readFileSync(this.stubPath, 'utf-8');
|
|
82
|
+
|
|
83
|
+
// Replace Placeholders
|
|
84
|
+
// Regex /g ensures it replaces ALL instances (e.g. extending Factory<Model> AND importing Model)
|
|
85
|
+
content = content.replace(/{{FactoryName}}/g, this.factoryName);
|
|
86
|
+
content = content.replace(/{{ModelName}}/g, this.modelName);
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(this.targetPath, content);
|
|
89
|
+
|
|
90
|
+
Logger.info(`Factory Created: src/database/factories/${this.factoryName}.ts`);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
Logger.error('Failed to create factory:', error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
new FactoryGenerator().run();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Logger from '../src/utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
class MigrationGenerator {
|
|
6
|
+
private nameArgument: string;
|
|
7
|
+
private migrationName: string;
|
|
8
|
+
private className: string;
|
|
9
|
+
private tableName: string;
|
|
10
|
+
private migrationsDir: string;
|
|
11
|
+
private stubPath: string;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.nameArgument = process.argv[2];
|
|
15
|
+
this.migrationsDir = path.join(process.cwd(), 'src', 'database', 'migrations');
|
|
16
|
+
this.stubPath = path.join(process.cwd(), 'scripts', 'stubs', 'migration.stub');
|
|
17
|
+
|
|
18
|
+
// Initialize properties safely
|
|
19
|
+
this.migrationName = '';
|
|
20
|
+
this.className = '';
|
|
21
|
+
this.tableName = 'table_name';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public run(): void {
|
|
25
|
+
if (!this.validateInput()) return;
|
|
26
|
+
|
|
27
|
+
this.parseNameArgument();
|
|
28
|
+
this.createMigrationFile();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private validateInput(): boolean {
|
|
32
|
+
if (!this.nameArgument) {
|
|
33
|
+
Logger.error('Please provide a migration name.');
|
|
34
|
+
console.log('\nUsage: npm run make:migration <name>');
|
|
35
|
+
console.log('Example: npm run make:migration create_products_table\n');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Transforms input "create_products_table" into:
|
|
43
|
+
* ClassName: CreateProductsTable
|
|
44
|
+
* TableName: products (tries to guess it)
|
|
45
|
+
*/
|
|
46
|
+
private parseNameArgument(): void {
|
|
47
|
+
this.migrationName = this.nameArgument.toLowerCase();
|
|
48
|
+
|
|
49
|
+
// 1. Generate Class Name (PascalCase)
|
|
50
|
+
this.className = this.migrationName
|
|
51
|
+
.split('_')
|
|
52
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
53
|
+
.join('');
|
|
54
|
+
|
|
55
|
+
// 2. Guess Table Name
|
|
56
|
+
const match = this.migrationName.match(/create_(.+)_table/);
|
|
57
|
+
if (match && match[1]) {
|
|
58
|
+
this.tableName = match[1];
|
|
59
|
+
} else {
|
|
60
|
+
this.tableName = this.migrationName;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generates YYYYMMDDHHMMSS format
|
|
66
|
+
*/
|
|
67
|
+
private getTimestamp(): string {
|
|
68
|
+
const now = new Date();
|
|
69
|
+
return now.toISOString().replace(/[-T:.Z]/g, '').slice(0, 14);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private createMigrationFile(): void {
|
|
73
|
+
try {
|
|
74
|
+
// 1. Generate Filename with Timestamp
|
|
75
|
+
const timestamp = this.getTimestamp();
|
|
76
|
+
const fileName = `${timestamp}-create_${this.migrationName}.js`;
|
|
77
|
+
const targetPath = path.join(this.migrationsDir, fileName);
|
|
78
|
+
|
|
79
|
+
// 2. Read Stub
|
|
80
|
+
let content = fs.readFileSync(this.stubPath, 'utf-8');
|
|
81
|
+
|
|
82
|
+
// 3. Replace Placeholders
|
|
83
|
+
content = content.replace(/{{ClassName}}/g, this.className);
|
|
84
|
+
content = content.replace(/{{TableName}}/g, this.tableName);
|
|
85
|
+
|
|
86
|
+
// 4. Write File
|
|
87
|
+
fs.writeFileSync(targetPath, content);
|
|
88
|
+
|
|
89
|
+
Logger.info(`Migration Created: src/database/migrations/${fileName}`);
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
Logger.error('Failed to create migration:', error);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
new MigrationGenerator().run();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Logger from '../src/utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
class ModelGenerator {
|
|
6
|
+
private modelName: string;
|
|
7
|
+
private stubPath: string;
|
|
8
|
+
private modelsDir: string;
|
|
9
|
+
private targetPath: string;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
// 1. Initialize Inputs
|
|
13
|
+
const rawName = process.argv[2];
|
|
14
|
+
|
|
15
|
+
if (rawName) {
|
|
16
|
+
this.modelName = this.formatName(rawName);
|
|
17
|
+
} else {
|
|
18
|
+
this.modelName = '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. Define Paths
|
|
22
|
+
this.stubPath = path.join(process.cwd(), 'scripts', 'stubs', 'model.stub');
|
|
23
|
+
this.modelsDir = path.join(process.cwd(), 'src', 'models');
|
|
24
|
+
|
|
25
|
+
// Only define target path if model name exists
|
|
26
|
+
this.targetPath = this.modelName
|
|
27
|
+
? path.join(this.modelsDir, `${this.modelName}.ts`)
|
|
28
|
+
: '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Main entry point to run the generator.
|
|
33
|
+
*/
|
|
34
|
+
public run(): void {
|
|
35
|
+
if (!this.validateInput()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.modelExists()) {
|
|
40
|
+
Logger.error(`Model "${this.modelName}" already exists at src/models/${this.modelName}.ts`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.createFile();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates command line arguments.
|
|
49
|
+
*/
|
|
50
|
+
private validateInput(): boolean {
|
|
51
|
+
if (!this.modelName) {
|
|
52
|
+
Logger.error('Please provide a model name.');
|
|
53
|
+
console.log('\nUsage: npm run make:model <ModelName>');
|
|
54
|
+
console.log('Example: npm run make:model Product\n');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if the file already exists.
|
|
62
|
+
*/
|
|
63
|
+
private modelExists(): boolean {
|
|
64
|
+
return fs.existsSync(this.targetPath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Helper to capitalize the first letter of a string.
|
|
69
|
+
*/
|
|
70
|
+
private formatName(str: string): string {
|
|
71
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Helper to pluralize table name (User -> users).
|
|
76
|
+
*/
|
|
77
|
+
private getTableName(): string {
|
|
78
|
+
return this.modelName.toLowerCase() + 's';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reads stub, replaces placeholders, and writes the file.
|
|
83
|
+
*/
|
|
84
|
+
private createFile(): void {
|
|
85
|
+
try {
|
|
86
|
+
// Read Stub
|
|
87
|
+
let content = fs.readFileSync(this.stubPath, 'utf-8');
|
|
88
|
+
|
|
89
|
+
// Replace Placeholders
|
|
90
|
+
const tableName = this.getTableName();
|
|
91
|
+
content = content.replace(/{{ModelName}}/g, this.modelName);
|
|
92
|
+
content = content.replace(/{{TableName}}/g, tableName);
|
|
93
|
+
|
|
94
|
+
// Write File
|
|
95
|
+
fs.writeFileSync(this.targetPath, content);
|
|
96
|
+
|
|
97
|
+
Logger.info(`Model Created: src/models/${this.modelName}.ts`);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
Logger.error('Failed to create model:', error);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
new ModelGenerator().run();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Logger from '../src/utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
class MaintenanceManager {
|
|
6
|
+
private lockFile: string;
|
|
7
|
+
private action: string;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.lockFile = path.join(process.cwd(), 'maintenance.lock');
|
|
11
|
+
// Get the command line argument (e.g., 'up' or 'down')
|
|
12
|
+
this.action = process.argv[2];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The main entry point for the script.
|
|
17
|
+
*/
|
|
18
|
+
public run(): void {
|
|
19
|
+
switch (this.action) {
|
|
20
|
+
case 'down':
|
|
21
|
+
this.enableMaintenance();
|
|
22
|
+
break;
|
|
23
|
+
case 'up':
|
|
24
|
+
this.disableMaintenance();
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
this.showUsage();
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Puts the application into maintenance mode.
|
|
34
|
+
*/
|
|
35
|
+
private enableMaintenance(): void {
|
|
36
|
+
try {
|
|
37
|
+
fs.writeFileSync(this.lockFile, 'MAINTENANCE_MODE_ACTIVE');
|
|
38
|
+
Logger.info('Application is now in MAINTENANCE MODE (503).');
|
|
39
|
+
Logger.info('Run "npm run up" to bring it back online.');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
Logger.error('Failed to enable maintenance mode:', error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Brings the application back online.
|
|
48
|
+
*/
|
|
49
|
+
private disableMaintenance(): void {
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync(this.lockFile)) {
|
|
52
|
+
fs.unlinkSync(this.lockFile);
|
|
53
|
+
Logger.info('Application is now LIVE.');
|
|
54
|
+
} else {
|
|
55
|
+
Logger.warn('Application was already live.');
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
Logger.error('Failed to disable maintenance mode:', error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Shows help instructions.
|
|
65
|
+
*/
|
|
66
|
+
private showUsage(): void {
|
|
67
|
+
Logger.info('\nUsage:');
|
|
68
|
+
Logger.info(' npm run down -> Put server in maintenance mode');
|
|
69
|
+
Logger.info(' npm run up -> Bring server back online\n');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
new MaintenanceManager().run();
|