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/bin/convert.js ADDED
@@ -0,0 +1,679 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout
10
+ });
11
+
12
+ function question(query) {
13
+ return new Promise(resolve => rl.question(query, resolve));
14
+ }
15
+
16
+ // Convertir un type SQL en type de cast JavaScript
17
+ function sqlTypeToCast(sqlType) {
18
+ const type = sqlType.toLowerCase();
19
+
20
+ if (type.includes('int') || type.includes('serial')) return 'int';
21
+ if (type.includes('float') || type.includes('double') || type.includes('decimal') || type.includes('numeric')) return 'float';
22
+ if (type.includes('bool')) return 'boolean';
23
+ if (type.includes('json')) return 'json';
24
+ if (type.includes('date') || type.includes('time')) return 'date';
25
+ if (type.includes('text') || type.includes('char') || type.includes('varchar')) return 'string';
26
+
27
+ return null;
28
+ }
29
+
30
+ // Parser une instruction CREATE TABLE
31
+ function parseCreateTable(sql) {
32
+ const tableMatch = sql.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?\s*\(/i);
33
+ if (!tableMatch) return null;
34
+
35
+ const tableName = tableMatch[1];
36
+ const columns = [];
37
+ const relations = [];
38
+
39
+ // Extraire les définitions de colonnes
40
+ const columnRegex = /`?(\w+)`?\s+(\w+(?:\(\d+(?:,\d+)?\))?)\s*([^,]*?)(?:[,)])/gi;
41
+ let match;
42
+
43
+ while ((match = columnRegex.exec(sql)) !== null) {
44
+ const [, columnName, columnType, constraints] = match;
45
+
46
+ // Ignorer les contraintes et index
47
+ if (columnName.toUpperCase() === 'PRIMARY' ||
48
+ columnName.toUpperCase() === 'UNIQUE' ||
49
+ columnName.toUpperCase() === 'KEY' ||
50
+ columnName.toUpperCase() === 'CONSTRAINT' ||
51
+ columnName.toUpperCase() === 'INDEX' ||
52
+ columnName.toUpperCase() === 'FOREIGN') {
53
+ continue;
54
+ }
55
+
56
+ const column = {
57
+ name: columnName,
58
+ type: columnType,
59
+ nullable: !constraints.toLowerCase().includes('not null'),
60
+ default: null,
61
+ autoIncrement: constraints.toLowerCase().includes('auto_increment'),
62
+ primary: constraints.toLowerCase().includes('primary key'),
63
+ unique: constraints.toLowerCase().includes('unique')
64
+ };
65
+
66
+ // Extraire la valeur par défaut
67
+ const defaultRegex = /DEFAULT\s+([^,\s]+)/i;
68
+ const defaultMatch = defaultRegex.exec(constraints);
69
+ if (defaultMatch) {
70
+ column.default = defaultMatch[1].replace(/['"]/g, '');
71
+ }
72
+
73
+ columns.push(column);
74
+
75
+ // Détecter les clés étrangères
76
+ if (columnName.endsWith('_id')) {
77
+ const relatedTable = columnName.replace(/_id$/, '') + 's';
78
+ relations.push({
79
+ type: 'belongsTo',
80
+ table: relatedTable,
81
+ foreignKey: columnName
82
+ });
83
+ }
84
+ }
85
+
86
+ // Détecter les clés étrangères explicites
87
+ const fkRegex = /FOREIGN\s+KEY\s*\(`?(\w+)`?\)\s*REFERENCES\s+`?(\w+)`?\s*\(`?(\w+)`?\)/gi;
88
+ while ((match = fkRegex.exec(sql)) !== null) {
89
+ const [, foreignKey, referencedTable, referencedColumn] = match;
90
+
91
+ const existingRelation = relations.find(r => r.foreignKey === foreignKey);
92
+ if (existingRelation) {
93
+ existingRelation.table = referencedTable;
94
+ existingRelation.relatedKey = referencedColumn;
95
+ } else {
96
+ relations.push({
97
+ type: 'belongsTo',
98
+ table: referencedTable,
99
+ foreignKey: foreignKey,
100
+ relatedKey: referencedColumn
101
+ });
102
+ }
103
+ }
104
+
105
+ return { tableName, columns, relations };
106
+ }
107
+
108
+ // Analyser toutes les tables pour détecter les relations
109
+ function analyzeRelations(allTablesInfo) {
110
+ const relationshipMap = {};
111
+
112
+ // Initialiser la map pour chaque table
113
+ allTablesInfo.forEach(tableInfo => {
114
+ relationshipMap[tableInfo.tableName] = {
115
+ belongsTo: [],
116
+ hasMany: [],
117
+ hasOne: [],
118
+ belongsToMany: []
119
+ };
120
+ });
121
+
122
+ // Analyser les relations belongsTo et leurs inverses
123
+ allTablesInfo.forEach(tableInfo => {
124
+ const { tableName, columns, relations } = tableInfo;
125
+
126
+ relations.forEach(rel => {
127
+ if (rel.type === 'belongsTo') {
128
+ // Ajouter la relation belongsTo
129
+ relationshipMap[tableName].belongsTo.push(rel);
130
+
131
+ // Détecter la relation inverse (hasMany ou hasOne)
132
+ const foreignKey = rel.foreignKey;
133
+ const relatedTable = rel.table;
134
+
135
+ // Vérifier si c'est une relation hasOne (clé unique) ou hasMany
136
+ const foreignColumn = columns.find(col => col.name === foreignKey);
137
+ const isUnique = foreignColumn?.unique;
138
+
139
+ if (isUnique) {
140
+ // Relation hasOne inverse
141
+ relationshipMap[relatedTable].hasOne.push({
142
+ table: tableName,
143
+ foreignKey: foreignKey,
144
+ localKey: rel.relatedKey || 'id'
145
+ });
146
+ } else {
147
+ // Relation hasMany inverse
148
+ relationshipMap[relatedTable].hasMany.push({
149
+ table: tableName,
150
+ foreignKey: foreignKey,
151
+ localKey: rel.relatedKey || 'id'
152
+ });
153
+ }
154
+ }
155
+ });
156
+ });
157
+
158
+ // Détecter les tables pivot pour relations belongsToMany
159
+ allTablesInfo.forEach(tableInfo => {
160
+ const { tableName, columns } = tableInfo;
161
+
162
+ // Une table pivot typique a:
163
+ // - Pas de clé primaire auto-increment OU clé primaire composite
164
+ // - Exactement 2 clés étrangères
165
+ // - Peu ou pas d'autres colonnes (sauf timestamps)
166
+ const foreignKeys = columns.filter(col => col.name.endsWith('_id'));
167
+ const nonForeignNonTimestamp = columns.filter(col =>
168
+ !col.name.endsWith('_id') &&
169
+ col.name !== 'id' &&
170
+ col.name !== 'created_at' &&
171
+ col.name !== 'updated_at'
172
+ );
173
+
174
+ const isPivotTable = foreignKeys.length === 2 && nonForeignNonTimestamp.length === 0;
175
+
176
+ if (isPivotTable) {
177
+ const [fk1, fk2] = foreignKeys;
178
+ const table1 = fk1.name.replace(/_id$/, '') + 's';
179
+ const table2 = fk2.name.replace(/_id$/, '') + 's';
180
+
181
+ // Ajouter la relation belongsToMany pour les deux tables
182
+ if (relationshipMap[table1] && relationshipMap[table2]) {
183
+ relationshipMap[table1].belongsToMany.push({
184
+ table: table2,
185
+ pivotTable: tableName,
186
+ foreignPivotKey: fk1.name,
187
+ relatedPivotKey: fk2.name
188
+ });
189
+
190
+ relationshipMap[table2].belongsToMany.push({
191
+ table: table1,
192
+ pivotTable: tableName,
193
+ foreignPivotKey: fk2.name,
194
+ relatedPivotKey: fk1.name
195
+ });
196
+ }
197
+ }
198
+ });
199
+
200
+ return relationshipMap;
201
+ }
202
+
203
+ // Générer le code du modèle
204
+ function generateModel(tableInfo, relationshipMap, options = {}) {
205
+ const { tableName, columns } = tableInfo;
206
+
207
+ // Nom de classe (PascalCase, singulier)
208
+ const className = tableName
209
+ .replace(/_/g, ' ')
210
+ .replace(/s$/, '')
211
+ .split(' ')
212
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
213
+ .join('');
214
+
215
+ // Colonnes fillable (exclure id, timestamps, clés étrangères si demandé)
216
+ const fillable = columns
217
+ .filter(col => {
218
+ if (col.name === 'id') return false;
219
+ if (col.name === 'created_at' || col.name === 'updated_at') return false;
220
+ if (options.excludeForeignKeys && col.name.endsWith('_id')) return false;
221
+ return true;
222
+ })
223
+ .map(col => col.name);
224
+
225
+ // Colonnes hidden (password, token, secret, etc.)
226
+ const hidden = columns
227
+ .filter(col => {
228
+ const name = col.name.toLowerCase();
229
+ return name.includes('password') ||
230
+ name.includes('token') ||
231
+ name.includes('secret') ||
232
+ name.includes('api_key');
233
+ })
234
+ .map(col => col.name);
235
+
236
+ // Casts
237
+ const casts = {};
238
+ columns.forEach(col => {
239
+ const cast = sqlTypeToCast(col.type);
240
+ if (cast && cast !== 'string') {
241
+ casts[col.name] = cast;
242
+ }
243
+ });
244
+
245
+ // Timestamps
246
+ const hasTimestamps = columns.some(col => col.name === 'created_at') &&
247
+ columns.some(col => col.name === 'updated_at');
248
+
249
+ // Clé primaire
250
+ const primaryKey = columns.find(col => col.primary)?.name || 'id';
251
+
252
+ // Générer le code
253
+ let code = `const { Model } = require('outlet-orm');\n\n`;
254
+
255
+ // Imports des modèles liés
256
+ const relatedModels = new Set();
257
+
258
+ // Obtenir toutes les relations pour cette table
259
+ const allRelations = relationshipMap[tableName] || {
260
+ belongsTo: [],
261
+ hasMany: [],
262
+ hasOne: [],
263
+ belongsToMany: []
264
+ };
265
+
266
+ // Collecter tous les modèles liés
267
+ [...allRelations.belongsTo, ...allRelations.hasMany, ...allRelations.hasOne, ...allRelations.belongsToMany].forEach(rel => {
268
+ const relatedClassName = rel.table
269
+ .replace(/_/g, ' ')
270
+ .replace(/s$/, '')
271
+ .split(' ')
272
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
273
+ .join('');
274
+ relatedModels.add(relatedClassName);
275
+ });
276
+
277
+ if (relatedModels.size > 0) {
278
+ code += `// Importer les modèles liés\n`;
279
+ relatedModels.forEach(model => {
280
+ code += `// const ${model} = require('./${model}');\n`;
281
+ });
282
+ code += `\n`;
283
+ }
284
+
285
+ code += `class ${className} extends Model {\n`;
286
+ code += ` static table = '${tableName}';\n`;
287
+
288
+ if (primaryKey !== 'id') {
289
+ code += ` static primaryKey = '${primaryKey}';\n`;
290
+ }
291
+
292
+ code += ` static timestamps = ${hasTimestamps};\n`;
293
+
294
+ if (fillable.length > 0) {
295
+ code += ` static fillable = [\n`;
296
+ fillable.forEach((col, i) => {
297
+ code += ` '${col}'${i < fillable.length - 1 ? ',' : ''}\n`;
298
+ });
299
+ code += ` ];\n`;
300
+ }
301
+
302
+ if (hidden.length > 0) {
303
+ code += ` static hidden = [\n`;
304
+ hidden.forEach((col, i) => {
305
+ code += ` '${col}'${i < hidden.length - 1 ? ',' : ''}\n`;
306
+ });
307
+ code += ` ];\n`;
308
+ }
309
+
310
+ if (Object.keys(casts).length > 0) {
311
+ code += ` static casts = {\n`;
312
+ Object.entries(casts).forEach(([col, type], i, arr) => {
313
+ code += ` ${col}: '${type}'${i < arr.length - 1 ? ',' : ''}\n`;
314
+ });
315
+ code += ` };\n`;
316
+ }
317
+
318
+ // Relations
319
+ const hasRelations = allRelations.belongsTo.length > 0 ||
320
+ allRelations.hasMany.length > 0 ||
321
+ allRelations.hasOne.length > 0 ||
322
+ allRelations.belongsToMany.length > 0;
323
+
324
+ if (hasRelations) {
325
+ code += `\n // Relations\n`;
326
+
327
+ // Relations belongsTo
328
+ allRelations.belongsTo.forEach(rel => {
329
+ const relatedClassName = rel.table
330
+ .replace(/_/g, ' ')
331
+ .replace(/s$/, '')
332
+ .split(' ')
333
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
334
+ .join('');
335
+
336
+ const methodName = rel.table.replace(/_/g, '').replace(/s$/, '');
337
+
338
+ code += ` ${methodName}() {\n`;
339
+ code += ` return this.belongsTo(${relatedClassName}, '${rel.foreignKey}'`;
340
+ if (rel.relatedKey && rel.relatedKey !== 'id') {
341
+ code += `, '${rel.relatedKey}'`;
342
+ }
343
+ code += `);\n`;
344
+ code += ` }\n\n`;
345
+ });
346
+
347
+ // Relations hasMany
348
+ allRelations.hasMany.forEach(rel => {
349
+ const relatedClassName = rel.table
350
+ .replace(/_/g, ' ')
351
+ .replace(/s$/, '')
352
+ .split(' ')
353
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
354
+ .join('');
355
+
356
+ const methodName = rel.table.replace(/_/g, '');
357
+
358
+ code += ` ${methodName}() {\n`;
359
+ code += ` return this.hasMany(${relatedClassName}, '${rel.foreignKey}'`;
360
+ if (rel.localKey && rel.localKey !== 'id') {
361
+ code += `, '${rel.localKey}'`;
362
+ }
363
+ code += `);\n`;
364
+ code += ` }\n\n`;
365
+ });
366
+
367
+ // Relations hasOne
368
+ allRelations.hasOne.forEach(rel => {
369
+ const relatedClassName = rel.table
370
+ .replace(/_/g, ' ')
371
+ .replace(/s$/, '')
372
+ .split(' ')
373
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
374
+ .join('');
375
+
376
+ const methodName = rel.table.replace(/_/g, '').replace(/s$/, '');
377
+
378
+ code += ` ${methodName}() {\n`;
379
+ code += ` return this.hasOne(${relatedClassName}, '${rel.foreignKey}'`;
380
+ if (rel.localKey && rel.localKey !== 'id') {
381
+ code += `, '${rel.localKey}'`;
382
+ }
383
+ code += `);\n`;
384
+ code += ` }\n\n`;
385
+ });
386
+
387
+ // Relations belongsToMany
388
+ allRelations.belongsToMany.forEach(rel => {
389
+ const relatedClassName = rel.table
390
+ .replace(/_/g, ' ')
391
+ .replace(/s$/, '')
392
+ .split(' ')
393
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
394
+ .join('');
395
+
396
+ const methodName = rel.table.replace(/_/g, '');
397
+
398
+ code += ` ${methodName}() {\n`;
399
+ code += ` return this.belongsToMany(${relatedClassName}, '${rel.pivotTable}', '${rel.foreignPivotKey}', '${rel.relatedPivotKey}');\n`;
400
+ code += ` }\n\n`;
401
+ });
402
+ }
403
+
404
+ code += `}\n\n`;
405
+ code += `module.exports = ${className};\n`;
406
+
407
+ return { className, code };
408
+ }
409
+
410
+ // Convertir un fichier SQL
411
+ async function convertFromFile() {
412
+ const sqlFile = await question('Chemin du fichier SQL: ');
413
+
414
+ if (!fs.existsSync(sqlFile)) {
415
+ console.error(`❌ Fichier non trouvé: ${sqlFile}`);
416
+ rl.close();
417
+ return;
418
+ }
419
+
420
+ const sqlContent = fs.readFileSync(sqlFile, 'utf8');
421
+
422
+ // Extraire toutes les instructions CREATE TABLE
423
+ const createTableRegex = /CREATE\s+TABLE\s+.*?;/gis;
424
+ const tables = sqlContent.match(createTableRegex) || [];
425
+
426
+ if (tables.length === 0) {
427
+ console.error('❌ Aucune instruction CREATE TABLE trouvée');
428
+ rl.close();
429
+ return;
430
+ }
431
+
432
+ console.log(`\n✅ ${tables.length} table(s) trouvée(s)\n`);
433
+
434
+ const outputDir = await question('Dossier de sortie pour les modèles (défaut: ./models): ') || './models';
435
+
436
+ if (!fs.existsSync(outputDir)) {
437
+ fs.mkdirSync(outputDir, { recursive: true });
438
+ }
439
+
440
+ const excludeFk = await question('Exclure les clés étrangères de fillable? (o/N): ');
441
+ const options = {
442
+ excludeForeignKeys: excludeFk.toLowerCase() === 'o' || excludeFk.toLowerCase() === 'y'
443
+ };
444
+
445
+ // Parser toutes les tables
446
+ const allTablesInfo = [];
447
+ tables.forEach(sql => {
448
+ const tableInfo = parseCreateTable(sql);
449
+ if (tableInfo) {
450
+ allTablesInfo.push(tableInfo);
451
+ }
452
+ });
453
+
454
+ // Analyser les relations entre toutes les tables
455
+ console.log('\n🔍 Analyse des relations...\n');
456
+ const relationshipMap = analyzeRelations(allTablesInfo);
457
+
458
+ // Générer les modèles avec toutes les relations
459
+ allTablesInfo.forEach(tableInfo => {
460
+ const { className, code } = generateModel(tableInfo, relationshipMap, options);
461
+ const filename = path.join(outputDir, `${className}.js`);
462
+
463
+ fs.writeFileSync(filename, code);
464
+
465
+ // Afficher les relations trouvées
466
+ const relations = relationshipMap[tableInfo.tableName];
467
+ const relationCount = relations.belongsTo.length + relations.hasMany.length +
468
+ relations.hasOne.length + relations.belongsToMany.length;
469
+
470
+ console.log(`✅ ${className}.js (${relationCount} relation${relationCount > 1 ? 's' : ''})`);
471
+ });
472
+
473
+ console.log(`\n✨ Conversion terminée! ${allTablesInfo.length} modèle(s) créé(s) dans ${outputDir}\n`);
474
+ rl.close();
475
+ }
476
+
477
+ // Récupérer la configuration de la base de données depuis l'utilisateur
478
+ async function getDatabaseConfig() {
479
+ const driver = await question('Driver (mysql/postgres/sqlite): ');
480
+ const dbConfig = { driver };
481
+
482
+ if (driver === 'mysql') {
483
+ dbConfig.host = await question('Host (défaut: localhost): ') || 'localhost';
484
+ dbConfig.port = await question('Port (défaut: 3306): ') || 3306;
485
+ dbConfig.database = await question('Database: ');
486
+ dbConfig.user = await question('User: ');
487
+ dbConfig.password = await question('Password: ');
488
+ } else if (driver === 'postgres' || driver === 'postgresql') {
489
+ dbConfig.host = await question('Host (défaut: localhost): ') || 'localhost';
490
+ dbConfig.port = await question('Port (défaut: 5432): ') || 5432;
491
+ dbConfig.database = await question('Database: ');
492
+ dbConfig.user = await question('User: ');
493
+ dbConfig.password = await question('Password: ');
494
+ } else if (driver === 'sqlite') {
495
+ dbConfig.filename = await question('Chemin du fichier SQLite: ');
496
+ } else {
497
+ throw new Error('Driver non supporté');
498
+ }
499
+
500
+ return dbConfig;
501
+ }
502
+
503
+ // Récupérer la liste des tables depuis la base de données
504
+ async function fetchTablesList(connection, driver) {
505
+ let tables = [];
506
+
507
+ if (driver === 'mysql') {
508
+ const result = await connection.query('SHOW TABLES');
509
+ const key = Object.keys(result[0])[0];
510
+ tables = result.map(row => row[key]);
511
+ } else if (driver === 'postgres' || driver === 'postgresql') {
512
+ const result = await connection.query(
513
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"
514
+ );
515
+ tables = result.map(row => row.table_name);
516
+ } else if (driver === 'sqlite') {
517
+ const result = await connection.query(
518
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
519
+ );
520
+ tables = result.map(row => row.name);
521
+ }
522
+
523
+ return tables;
524
+ }
525
+
526
+ // Récupérer le CREATE TABLE pour une table donnée
527
+ async function fetchCreateTableSql(connection, driver, tableName) {
528
+ let createTableSql = '';
529
+
530
+ if (driver === 'mysql') {
531
+ const result = await connection.query(`SHOW CREATE TABLE \`${tableName}\``);
532
+ createTableSql = result[0]['Create Table'];
533
+ } else if (driver === 'postgres' || driver === 'postgresql') {
534
+ // Pour PostgreSQL, construire le CREATE TABLE depuis information_schema
535
+ const columns = await connection.query(`
536
+ SELECT column_name, data_type, is_nullable, column_default
537
+ FROM information_schema.columns
538
+ WHERE table_name = '${tableName}'
539
+ ORDER BY ordinal_position
540
+ `);
541
+
542
+ createTableSql = `CREATE TABLE ${tableName} (\n`;
543
+ columns.forEach((col, i) => {
544
+ createTableSql += ` ${col.column_name} ${col.data_type}`;
545
+ if (col.is_nullable === 'NO') createTableSql += ' NOT NULL';
546
+ if (col.column_default) createTableSql += ` DEFAULT ${col.column_default}`;
547
+ if (i < columns.length - 1) createTableSql += ',';
548
+ createTableSql += '\n';
549
+ });
550
+ createTableSql += ');';
551
+ } else if (driver === 'sqlite') {
552
+ const result = await connection.query(`SELECT sql FROM sqlite_master WHERE type='table' AND name='${tableName}'`);
553
+ createTableSql = result[0].sql;
554
+ }
555
+
556
+ return createTableSql;
557
+ }
558
+
559
+ // Convertir depuis une base de données connectée
560
+ async function convertFromDatabase() {
561
+ console.log('\n📊 Conversion depuis une base de données\n');
562
+
563
+ try {
564
+ // Récupérer la configuration
565
+ const dbConfig = await getDatabaseConfig();
566
+
567
+ // Connexion à la base de données
568
+ const { DatabaseConnection } = require('../src/index.js');
569
+ const connection = new DatabaseConnection(dbConfig);
570
+
571
+ console.log('\n🔄 Connexion à la base de données...');
572
+ await connection.connect();
573
+ console.log('✅ Connecté!\n');
574
+
575
+ // Récupérer la liste des tables
576
+ const tables = await fetchTablesList(connection, dbConfig.driver);
577
+
578
+ if (tables.length === 0) {
579
+ console.error('❌ Aucune table trouvée');
580
+ await connection.close();
581
+ rl.close();
582
+ return;
583
+ }
584
+
585
+ console.log(`✅ ${tables.length} table(s) trouvée(s):\n`);
586
+ tables.forEach((table, i) => console.log(` ${i + 1}. ${table}`));
587
+ console.log('');
588
+
589
+ const outputDir = await question('Dossier de sortie pour les modèles (défaut: ./models): ') || './models';
590
+
591
+ if (!fs.existsSync(outputDir)) {
592
+ fs.mkdirSync(outputDir, { recursive: true });
593
+ }
594
+
595
+ const excludeFk = await question('Exclure les clés étrangères de fillable? (o/N): ');
596
+ const options = {
597
+ excludeForeignKeys: excludeFk.toLowerCase() === 'o' || excludeFk.toLowerCase() === 'y'
598
+ };
599
+
600
+ // Parser toutes les tables
601
+ console.log('\n🔍 Récupération des schémas...\n');
602
+ const allTablesInfo = [];
603
+
604
+ // Pour chaque table, récupérer le CREATE TABLE
605
+ for (const tableName of tables) {
606
+ const createTableSql = await fetchCreateTableSql(connection, dbConfig.driver, tableName);
607
+ const tableInfo = parseCreateTable(createTableSql);
608
+ if (tableInfo) {
609
+ allTablesInfo.push(tableInfo);
610
+ }
611
+ }
612
+
613
+ // Analyser les relations entre toutes les tables
614
+ console.log('🔍 Analyse des relations...\n');
615
+ const relationshipMap = analyzeRelations(allTablesInfo);
616
+
617
+ // Générer les modèles avec toutes les relations
618
+ allTablesInfo.forEach(tableInfo => {
619
+ const { className, code } = generateModel(tableInfo, relationshipMap, options);
620
+ const filename = path.join(outputDir, `${className}.js`);
621
+
622
+ fs.writeFileSync(filename, code);
623
+
624
+ // Afficher les relations trouvées
625
+ const relations = relationshipMap[tableInfo.tableName];
626
+ const relationCount = relations.belongsTo.length + relations.hasMany.length +
627
+ relations.hasOne.length + relations.belongsToMany.length;
628
+
629
+ console.log(`✅ ${className}.js (${relationCount} relation${relationCount > 1 ? 's' : ''})`);
630
+ });
631
+
632
+ console.log(`\n✨ Conversion terminée! ${allTablesInfo.length} modèle(s) créé(s) dans ${outputDir}\n`);
633
+
634
+ await connection.close();
635
+ } catch (error) {
636
+ console.error('❌ Erreur:', error.message);
637
+ if (error.message === 'Driver non supporté') {
638
+ rl.close();
639
+ return;
640
+ }
641
+ }
642
+
643
+ rl.close();
644
+ }
645
+
646
+ // Menu principal
647
+ async function main() {
648
+ console.log('\n╔═══════════════════════════════════════╗');
649
+ console.log('║ Outlet ORM - Convertisseur SQL ║');
650
+ console.log('╚═══════════════════════════════════════╝\n');
651
+
652
+ console.log('Choisissez une option:\n');
653
+ console.log(' 1. Convertir depuis un fichier SQL');
654
+ console.log(' 2. Convertir depuis une base de données connectée');
655
+ console.log(' 3. Quitter\n');
656
+
657
+ const choice = await question('Votre choix: ');
658
+
659
+ switch (choice) {
660
+ case '1':
661
+ await convertFromFile();
662
+ break;
663
+ case '2':
664
+ await convertFromDatabase();
665
+ break;
666
+ case '3':
667
+ console.log('Au revoir! 👋\n');
668
+ rl.close();
669
+ break;
670
+ default:
671
+ console.log('❌ Choix invalide\n');
672
+ rl.close();
673
+ }
674
+ }
675
+
676
+ main().catch(error => {
677
+ console.error('❌ Erreur:', error.message);
678
+ rl.close();
679
+ });