outlet-orm 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +705 -0
- package/bin/convert.js +679 -0
- package/bin/init.js +190 -0
- package/bin/migrate.js +442 -0
- package/lib/Database/DatabaseConnection.js +4 -0
- package/lib/Migrations/Migration.js +48 -0
- package/lib/Migrations/MigrationManager.js +326 -0
- package/lib/Schema/Schema.js +790 -0
- package/package.json +75 -0
- package/src/DatabaseConnection.js +697 -0
- package/src/Model.js +659 -0
- package/src/QueryBuilder.js +710 -0
- package/src/Relations/BelongsToManyRelation.js +466 -0
- package/src/Relations/BelongsToRelation.js +127 -0
- package/src/Relations/HasManyRelation.js +125 -0
- package/src/Relations/HasManyThroughRelation.js +112 -0
- package/src/Relations/HasOneRelation.js +114 -0
- package/src/Relations/HasOneThroughRelation.js +105 -0
- package/src/Relations/MorphManyRelation.js +69 -0
- package/src/Relations/MorphOneRelation.js +68 -0
- package/src/Relations/MorphToRelation.js +110 -0
- package/src/Relations/Relation.js +31 -0
- package/src/index.js +23 -0
- package/types/index.d.ts +272 -0
package/bin/init.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Script d'initialisation pour le package ORM
|
|
5
|
+
* Ce script aide à configurer rapidement un projet avec l'ORM
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
18
|
+
|
|
19
|
+
async function init() {
|
|
20
|
+
console.log('\n🚀 Bienvenue dans l\'assistant de configuration Outlet ORM!\n');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Database driver
|
|
24
|
+
console.log('Quel driver de base de données souhaitez-vous utiliser?');
|
|
25
|
+
console.log('1. MySQL');
|
|
26
|
+
console.log('2. PostgreSQL');
|
|
27
|
+
console.log('3. SQLite');
|
|
28
|
+
const driverChoice = await question('\nVotre choix (1-3): ');
|
|
29
|
+
|
|
30
|
+
const drivers = {
|
|
31
|
+
'1': { name: 'mysql', package: 'mysql2', defaultPort: 3306 },
|
|
32
|
+
'2': { name: 'postgres', package: 'pg', defaultPort: 5432 },
|
|
33
|
+
'3': { name: 'sqlite', package: 'sqlite3', defaultPort: null }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const selectedDriver = drivers[driverChoice];
|
|
37
|
+
if (!selectedDriver) {
|
|
38
|
+
console.error('❌ Choix invalide!');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Database configuration
|
|
43
|
+
let config = {
|
|
44
|
+
driver: selectedDriver.name
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (selectedDriver.name !== 'sqlite') {
|
|
48
|
+
config.host = await question('Host (localhost): ') || 'localhost';
|
|
49
|
+
config.port = await question(`Port (${selectedDriver.defaultPort}): `) || selectedDriver.defaultPort;
|
|
50
|
+
config.database = await question('Nom de la base de données: ');
|
|
51
|
+
config.user = await question('Utilisateur: ');
|
|
52
|
+
config.password = await question('Mot de passe: ');
|
|
53
|
+
} else {
|
|
54
|
+
config.database = await question('Chemin du fichier SQLite (./database.sqlite): ') || './database.sqlite';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Ask to generate a .env file
|
|
58
|
+
const generateEnv = (await question('\nSouhaitez-vous générer un fichier .env avec ces paramètres ? (oui/non) [oui]: ')).trim().toLowerCase();
|
|
59
|
+
const wantEnv = generateEnv === '' || generateEnv === 'oui' || generateEnv === 'o' || generateEnv === 'yes' || generateEnv === 'y';
|
|
60
|
+
|
|
61
|
+
if (wantEnv) {
|
|
62
|
+
const envLines = [];
|
|
63
|
+
envLines.push(`DB_DRIVER=${config.driver}`);
|
|
64
|
+
if (config.driver !== 'sqlite') {
|
|
65
|
+
envLines.push(`DB_HOST=${config.host || 'localhost'}`);
|
|
66
|
+
envLines.push(`DB_PORT=${config.port || selectedDriver.defaultPort || ''}`);
|
|
67
|
+
envLines.push(`DB_USER=${config.user || ''}`);
|
|
68
|
+
envLines.push(`DB_PASSWORD=${config.password || ''}`);
|
|
69
|
+
envLines.push(`DB_DATABASE=${config.database || ''}`);
|
|
70
|
+
} else {
|
|
71
|
+
envLines.push(`DB_FILE=${config.database}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
75
|
+
if (fs.existsSync(envPath)) {
|
|
76
|
+
console.log('ℹ️ .env existe déjà, génération ignorée.');
|
|
77
|
+
} else {
|
|
78
|
+
fs.writeFileSync(envPath, envLines.join('\n') + '\n');
|
|
79
|
+
console.log(`✅ Fichier .env créé: ${envPath}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Generate config file
|
|
84
|
+
const configContent = `const { DatabaseConnection } = require('outlet-orm');
|
|
85
|
+
|
|
86
|
+
// Configuration de la base de données
|
|
87
|
+
const db = new DatabaseConnection(${JSON.stringify(config, null, 2)});
|
|
88
|
+
|
|
89
|
+
module.exports = db;
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const configPath = path.join(process.cwd(), 'database.js');
|
|
93
|
+
fs.writeFileSync(configPath, configContent);
|
|
94
|
+
console.log(`\n✅ Fichier de configuration créé: ${configPath}`);
|
|
95
|
+
|
|
96
|
+
// Generate example model
|
|
97
|
+
const modelContent = `const { Model } = require('outlet-orm');
|
|
98
|
+
const db = require('./database');
|
|
99
|
+
|
|
100
|
+
class User extends Model {
|
|
101
|
+
static table = 'users';
|
|
102
|
+
static fillable = ['name', 'email', 'password'];
|
|
103
|
+
static hidden = ['password'];
|
|
104
|
+
static casts = {
|
|
105
|
+
id: 'int',
|
|
106
|
+
email_verified: 'boolean'
|
|
107
|
+
};
|
|
108
|
+
static connection = db;
|
|
109
|
+
|
|
110
|
+
// Définissez vos relations ici
|
|
111
|
+
// posts() {
|
|
112
|
+
// return this.hasMany(Post, 'user_id');
|
|
113
|
+
// }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = User;
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const modelPath = path.join(process.cwd(), 'User.js');
|
|
120
|
+
fs.writeFileSync(modelPath, modelContent);
|
|
121
|
+
console.log(`✅ Modèle exemple créé: ${modelPath}`);
|
|
122
|
+
|
|
123
|
+
// Generate usage example
|
|
124
|
+
const usageContent = `const User = require('./User');
|
|
125
|
+
|
|
126
|
+
async function main() {
|
|
127
|
+
try {
|
|
128
|
+
// Exemple: Créer un utilisateur
|
|
129
|
+
const user = await User.create({
|
|
130
|
+
name: 'John Doe',
|
|
131
|
+
email: 'john@example.com',
|
|
132
|
+
password: 'secret123'
|
|
133
|
+
});
|
|
134
|
+
console.log('Utilisateur créé:', user.toJSON());
|
|
135
|
+
|
|
136
|
+
// Exemple: Rechercher des utilisateurs
|
|
137
|
+
const users = await User.all();
|
|
138
|
+
console.log('Tous les utilisateurs:', users.length);
|
|
139
|
+
|
|
140
|
+
// Exemple: Requête avec conditions
|
|
141
|
+
const activeUsers = await User
|
|
142
|
+
.where('status', 'active')
|
|
143
|
+
.orderBy('name')
|
|
144
|
+
.get();
|
|
145
|
+
console.log('Utilisateurs actifs:', activeUsers.length);
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('Erreur:', error.message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main();
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
const usagePath = path.join(process.cwd(), 'example.js');
|
|
156
|
+
fs.writeFileSync(usagePath, usageContent);
|
|
157
|
+
console.log(`✅ Exemple d'utilisation créé: ${usagePath}`);
|
|
158
|
+
|
|
159
|
+
// Optionally skip package init/install in non-interactive or test context
|
|
160
|
+
const skipInstall = process.env.OUTLET_INIT_NO_INSTALL === '1';
|
|
161
|
+
if (!skipInstall) {
|
|
162
|
+
// Check if package needs to be installed
|
|
163
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
164
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
165
|
+
console.log('\n⚠️ Aucun package.json trouvé. Initialisation...');
|
|
166
|
+
require('child_process').execSync('npm init -y', { stdio: 'inherit' });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`\n📦 Installation du driver ${selectedDriver.package}...`);
|
|
170
|
+
require('child_process').execSync(`npm install ${selectedDriver.package}`, { stdio: 'inherit' });
|
|
171
|
+
} else {
|
|
172
|
+
console.log('\n⏭️ Installation du driver ignorée (OUTLET_INIT_NO_INSTALL=1).');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('\n✨ Configuration terminée!\n');
|
|
176
|
+
console.log('Prochaines étapes:');
|
|
177
|
+
console.log('1. Créez votre schéma de base de données');
|
|
178
|
+
console.log('2. Modifiez User.js selon vos besoins');
|
|
179
|
+
console.log('3. Exécutez example.js: node example.js');
|
|
180
|
+
console.log('\n📚 Documentation: https://github.com/yourusername/outlet-orm');
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('\n❌ Erreur:', error.message);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
} finally {
|
|
186
|
+
rl.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
init();
|
package/bin/migrate.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* outlet-migrate CLI
|
|
5
|
+
* Migration management tool for outlet-orm
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const fs = require('fs').promises;
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function question(query) {
|
|
18
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
console.log('\n╔═══════════════════════════════════════╗');
|
|
23
|
+
console.log('║ Outlet ORM - Migration Manager ║');
|
|
24
|
+
console.log('╚═══════════════════════════════════════╝\n');
|
|
25
|
+
|
|
26
|
+
const command = process.argv[2];
|
|
27
|
+
|
|
28
|
+
if (command === 'make') {
|
|
29
|
+
await makeMigration();
|
|
30
|
+
rl.close();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Support non-interactive commands for automation and CI
|
|
35
|
+
const nonInteractive = new Set(['migrate', 'up', 'rollback', 'reset', 'refresh', 'fresh', 'status']);
|
|
36
|
+
if (nonInteractive.has(command)) {
|
|
37
|
+
const flags = parseFlags(process.argv.slice(3));
|
|
38
|
+
await runNonInteractive(command, flags);
|
|
39
|
+
rl.close();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback to interactive menu
|
|
44
|
+
await runMigrationCommands();
|
|
45
|
+
|
|
46
|
+
rl.close();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a new migration file
|
|
51
|
+
*/
|
|
52
|
+
async function makeMigration() {
|
|
53
|
+
const migrationName = process.argv[3];
|
|
54
|
+
|
|
55
|
+
if (!migrationName) {
|
|
56
|
+
console.error('✗ Error: Migration name is required');
|
|
57
|
+
console.log('Usage: outlet-migrate make <migration_name>');
|
|
58
|
+
console.log('Example: outlet-migrate make create_users_table');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const migrationsDir = path.join(process.cwd(), 'database', 'migrations');
|
|
63
|
+
|
|
64
|
+
// Create migrations directory if it doesn't exist
|
|
65
|
+
try {
|
|
66
|
+
await fs.mkdir(migrationsDir, { recursive: true });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// Directory already exists - ignore error as recursive: true handles this
|
|
69
|
+
if (error.code !== 'EEXIST') {
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Generate timestamp
|
|
75
|
+
const timestamp = new Date().toISOString()
|
|
76
|
+
.replace(/[-:]/g, '')
|
|
77
|
+
.replace(/T/, '_')
|
|
78
|
+
.replace(/\..+/, '');
|
|
79
|
+
|
|
80
|
+
const fileName = `${timestamp}_${migrationName}.js`;
|
|
81
|
+
const filePath = path.join(migrationsDir, fileName);
|
|
82
|
+
|
|
83
|
+
// Determine if it's a create or alter migration
|
|
84
|
+
const isCreate = migrationName.includes('create_');
|
|
85
|
+
const tableName = extractTableName(migrationName);
|
|
86
|
+
|
|
87
|
+
const template = isCreate
|
|
88
|
+
? getCreateMigrationTemplate(tableName)
|
|
89
|
+
: getAlterMigrationTemplate(tableName);
|
|
90
|
+
|
|
91
|
+
await fs.writeFile(filePath, template);
|
|
92
|
+
|
|
93
|
+
console.log(`✓ Migration created: ${fileName}`);
|
|
94
|
+
console.log(` Location: ${filePath}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract table name from migration name
|
|
99
|
+
*/
|
|
100
|
+
function extractTableName(migrationName) {
|
|
101
|
+
// Extract table name from patterns like:
|
|
102
|
+
// create_users_table -> users
|
|
103
|
+
// add_email_to_users_table -> users
|
|
104
|
+
// alter_users_table -> users
|
|
105
|
+
|
|
106
|
+
const patterns = [
|
|
107
|
+
/create_(\w+)_table/,
|
|
108
|
+
/to_(\w+)_table/,
|
|
109
|
+
/alter_(\w+)_table/,
|
|
110
|
+
/(\w+)_table/
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const pattern of patterns) {
|
|
114
|
+
const match = migrationName.match(pattern);
|
|
115
|
+
if (match) {
|
|
116
|
+
return match[1];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return 'table_name';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get migration template for creating a table
|
|
125
|
+
*/
|
|
126
|
+
function getCreateMigrationTemplate(tableName) {
|
|
127
|
+
return `/**
|
|
128
|
+
* Migration: Create ${tableName} table
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
const Migration = require('../../lib/Migrations/Migration');
|
|
132
|
+
|
|
133
|
+
class Create${capitalize(tableName)}Table extends Migration {
|
|
134
|
+
/**
|
|
135
|
+
* Run the migrations
|
|
136
|
+
*/
|
|
137
|
+
async up() {
|
|
138
|
+
const schema = this.getSchema();
|
|
139
|
+
|
|
140
|
+
await schema.create('${tableName}', (table) => {
|
|
141
|
+
table.id();
|
|
142
|
+
table.string('name');
|
|
143
|
+
table.timestamps();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reverse the migrations
|
|
149
|
+
*/
|
|
150
|
+
async down() {
|
|
151
|
+
const schema = this.getSchema();
|
|
152
|
+
await schema.dropIfExists('${tableName}');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = Create${capitalize(tableName)}Table;
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get migration template for altering a table
|
|
162
|
+
*/
|
|
163
|
+
function getAlterMigrationTemplate(tableName) {
|
|
164
|
+
return `/**
|
|
165
|
+
* Migration: Alter ${tableName} table
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
const Migration = require('../../lib/Migrations/Migration');
|
|
169
|
+
|
|
170
|
+
class Alter${capitalize(tableName)}Table extends Migration {
|
|
171
|
+
/**
|
|
172
|
+
* Run the migrations
|
|
173
|
+
*/
|
|
174
|
+
async up() {
|
|
175
|
+
const schema = this.getSchema();
|
|
176
|
+
|
|
177
|
+
await schema.table('${tableName}', (table) => {
|
|
178
|
+
// Add your column modifications here
|
|
179
|
+
// table.string('new_column');
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Reverse the migrations
|
|
185
|
+
*/
|
|
186
|
+
async down() {
|
|
187
|
+
const schema = this.getSchema();
|
|
188
|
+
|
|
189
|
+
await schema.table('${tableName}', (table) => {
|
|
190
|
+
// Reverse your column modifications here
|
|
191
|
+
// table.dropColumn('new_column');
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = Alter${capitalize(tableName)}Table;
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Capitalize first letter
|
|
202
|
+
*/
|
|
203
|
+
function capitalize(str) {
|
|
204
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Simple flag parser for CLI args
|
|
209
|
+
* Supports formats:
|
|
210
|
+
* --key=value, --key value, -k value, and boolean flags like --yes/-y
|
|
211
|
+
*/
|
|
212
|
+
function parseFlags(argv) {
|
|
213
|
+
const text = ` ${argv.join(' ')} `;
|
|
214
|
+
const flags = {};
|
|
215
|
+
// Booleans
|
|
216
|
+
if (/(^|\s)(--yes|-y)(\s|$)/.test(text)) flags.yes = true;
|
|
217
|
+
if (/(^|\s)(--force|-f)(\s|$)/.test(text)) flags.force = true;
|
|
218
|
+
// Steps with value: supports "--steps N", "--steps=N", "-s N"
|
|
219
|
+
const stepsRe = /(?:--steps(?:=|\s+)|-s\s+)(\S+)/;
|
|
220
|
+
const stepsMatch = stepsRe.exec(text);
|
|
221
|
+
if (stepsMatch) flags.steps = coerce(stepsMatch[1]);
|
|
222
|
+
return flags;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function coerce(val) {
|
|
226
|
+
if (val === 'true') return true;
|
|
227
|
+
if (val === 'false') return false;
|
|
228
|
+
const n = Number(val);
|
|
229
|
+
return Number.isNaN(n) ? val : n;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run migration commands non-interactively
|
|
234
|
+
*/
|
|
235
|
+
async function runNonInteractive(cmd, flags) {
|
|
236
|
+
// Load database configuration
|
|
237
|
+
const dbConfigPath = path.join(process.cwd(), 'database', 'config.js');
|
|
238
|
+
|
|
239
|
+
// Prefer database/config.js; if missing, allow env-based config via .env
|
|
240
|
+
let dbConfig;
|
|
241
|
+
try {
|
|
242
|
+
dbConfig = require(dbConfigPath);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
// Fallback to env-based configuration
|
|
245
|
+
require('dotenv').config();
|
|
246
|
+
const env = process.env || {};
|
|
247
|
+
dbConfig = {
|
|
248
|
+
driver: env.DB_DRIVER || env.DATABASE_DRIVER,
|
|
249
|
+
host: env.DB_HOST,
|
|
250
|
+
port: env.DB_PORT ? Number(env.DB_PORT) : undefined,
|
|
251
|
+
user: env.DB_USER || env.DB_USERNAME,
|
|
252
|
+
password: env.DB_PASSWORD,
|
|
253
|
+
database: env.DB_DATABASE || env.DB_NAME || env.DB_FILE || env.SQLITE_DB || env.SQLITE_FILENAME
|
|
254
|
+
};
|
|
255
|
+
if (!dbConfig.driver) {
|
|
256
|
+
console.error('\n✗ Error: Could not load database configuration');
|
|
257
|
+
console.error(` Make sure ${dbConfigPath} exists OR provide .env variables like DB_DRIVER, DB_HOST, DB_DATABASE`);
|
|
258
|
+
console.error(' Run "outlet-init" to create the configuration');
|
|
259
|
+
console.error(` Details: ${error.message}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { DatabaseConnection } = require('../lib/Database/DatabaseConnection');
|
|
265
|
+
const MigrationManager = require('../lib/Migrations/MigrationManager');
|
|
266
|
+
|
|
267
|
+
const connection = new DatabaseConnection(dbConfig);
|
|
268
|
+
await connection.connect();
|
|
269
|
+
|
|
270
|
+
const manager = new MigrationManager(connection);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
switch (cmd) {
|
|
274
|
+
case 'migrate':
|
|
275
|
+
case 'up':
|
|
276
|
+
await manager.run();
|
|
277
|
+
break;
|
|
278
|
+
|
|
279
|
+
case 'rollback': {
|
|
280
|
+
const steps = Number(flags.steps) || 1;
|
|
281
|
+
await manager.rollback(steps);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
case 'reset': {
|
|
286
|
+
if (flags.yes || flags.force) {
|
|
287
|
+
await manager.reset();
|
|
288
|
+
} else {
|
|
289
|
+
console.error('✗ Refused to reset without --yes');
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case 'refresh': {
|
|
295
|
+
if (flags.yes || flags.force) {
|
|
296
|
+
await manager.refresh();
|
|
297
|
+
} else {
|
|
298
|
+
console.error('✗ Refused to refresh without --yes');
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case 'fresh': {
|
|
304
|
+
if (flags.yes || flags.force) {
|
|
305
|
+
await manager.fresh();
|
|
306
|
+
} else {
|
|
307
|
+
console.error('✗ Refused to fresh without --yes');
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case 'status':
|
|
313
|
+
await manager.status();
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
default:
|
|
317
|
+
console.error(`✗ Unknown command: ${cmd}`);
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('\n✗ Migration error:', error.message);
|
|
321
|
+
console.error(error.stack);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
await connection.disconnect();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Run migration commands (migrate, rollback, etc.)
|
|
329
|
+
*/
|
|
330
|
+
async function runMigrationCommands() {
|
|
331
|
+
console.log('Select a migration command:\n');
|
|
332
|
+
console.log('1. migrate - Run all pending migrations');
|
|
333
|
+
console.log('2. rollback - Rollback the last batch of migrations');
|
|
334
|
+
console.log('3. reset - Rollback all migrations');
|
|
335
|
+
console.log('4. refresh - Reset and re-run all migrations');
|
|
336
|
+
console.log('5. fresh - Drop all tables and re-run migrations');
|
|
337
|
+
console.log('6. status - Show migration status');
|
|
338
|
+
console.log('0. Exit\n');
|
|
339
|
+
|
|
340
|
+
const choice = await question('Enter your choice: ');
|
|
341
|
+
|
|
342
|
+
if (choice === '0') {
|
|
343
|
+
console.log('Goodbye!');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Load database configuration
|
|
348
|
+
const dbConfigPath = path.join(process.cwd(), 'database', 'config.js');
|
|
349
|
+
|
|
350
|
+
let dbConfig;
|
|
351
|
+
try {
|
|
352
|
+
dbConfig = require(dbConfigPath);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
require('dotenv').config();
|
|
355
|
+
const env = process.env || {};
|
|
356
|
+
dbConfig = {
|
|
357
|
+
driver: env.DB_DRIVER || env.DATABASE_DRIVER,
|
|
358
|
+
host: env.DB_HOST,
|
|
359
|
+
port: env.DB_PORT ? Number(env.DB_PORT) : undefined,
|
|
360
|
+
user: env.DB_USER || env.DB_USERNAME,
|
|
361
|
+
password: env.DB_PASSWORD,
|
|
362
|
+
database: env.DB_DATABASE || env.DB_NAME || env.DB_FILE || env.SQLITE_DB || env.SQLITE_FILENAME
|
|
363
|
+
};
|
|
364
|
+
if (!dbConfig.driver) {
|
|
365
|
+
console.error('\n✗ Error: Could not load database configuration');
|
|
366
|
+
console.error(` Make sure ${dbConfigPath} exists OR provide .env variables like DB_DRIVER, DB_HOST, DB_DATABASE`);
|
|
367
|
+
console.error(' Run "outlet-init" to create the configuration');
|
|
368
|
+
console.error(` Details: ${error.message}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const { DatabaseConnection } = require('../lib/Database/DatabaseConnection');
|
|
374
|
+
const MigrationManager = require('../lib/Migrations/MigrationManager');
|
|
375
|
+
|
|
376
|
+
const connection = new DatabaseConnection(dbConfig);
|
|
377
|
+
await connection.connect();
|
|
378
|
+
|
|
379
|
+
const manager = new MigrationManager(connection);
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
switch (choice) {
|
|
383
|
+
case '1':
|
|
384
|
+
await manager.run();
|
|
385
|
+
break;
|
|
386
|
+
|
|
387
|
+
case '2': {
|
|
388
|
+
const steps = await question('How many batches to rollback? (default: 1): ');
|
|
389
|
+
await manager.rollback(parseInt(steps) || 1);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case '3': {
|
|
394
|
+
const confirmReset = await question('Are you sure you want to reset all migrations? (yes/no): ');
|
|
395
|
+
if (confirmReset.toLowerCase() === 'yes') {
|
|
396
|
+
await manager.reset();
|
|
397
|
+
} else {
|
|
398
|
+
console.log('Reset cancelled');
|
|
399
|
+
}
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
case '4': {
|
|
404
|
+
const confirmRefresh = await question('Are you sure you want to refresh all migrations? (yes/no): ');
|
|
405
|
+
if (confirmRefresh.toLowerCase() === 'yes') {
|
|
406
|
+
await manager.refresh();
|
|
407
|
+
} else {
|
|
408
|
+
console.log('Refresh cancelled');
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
case '5': {
|
|
414
|
+
const confirmFresh = await question('⚠️ WARNING: This will DROP ALL TABLES! Continue? (yes/no): ');
|
|
415
|
+
if (confirmFresh.toLowerCase() === 'yes') {
|
|
416
|
+
await manager.fresh();
|
|
417
|
+
} else {
|
|
418
|
+
console.log('Fresh cancelled');
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
case '6':
|
|
424
|
+
await manager.status();
|
|
425
|
+
break;
|
|
426
|
+
|
|
427
|
+
default:
|
|
428
|
+
console.log('Invalid choice');
|
|
429
|
+
}
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error('\n✗ Migration error:', error.message);
|
|
432
|
+
console.error(error.stack);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
await connection.disconnect();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Run the CLI
|
|
439
|
+
main().catch(error => {
|
|
440
|
+
console.error('Fatal error:', error);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Migration Class
|
|
3
|
+
* All migrations should extend this class
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class Migration {
|
|
7
|
+
constructor(connection) {
|
|
8
|
+
this.connection = connection;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run the migrations
|
|
13
|
+
*/
|
|
14
|
+
async up() {
|
|
15
|
+
throw new Error('Migration up() method must be implemented');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reverse the migrations
|
|
20
|
+
*/
|
|
21
|
+
async down() {
|
|
22
|
+
throw new Error('Migration down() method must be implemented');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the migration name
|
|
27
|
+
*/
|
|
28
|
+
static getName() {
|
|
29
|
+
return this.name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execute raw SQL
|
|
34
|
+
*/
|
|
35
|
+
async execute(sql) {
|
|
36
|
+
return await this.connection.execute(sql);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the Schema builder
|
|
41
|
+
*/
|
|
42
|
+
getSchema() {
|
|
43
|
+
const { Schema } = require('../Schema/Schema');
|
|
44
|
+
return new Schema(this.connection);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Migration;
|