@cheetah.js/cli 0.1.23 → 0.1.25
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/dist/bin.js +2 -0
- package/package.json +12 -8
- package/src/bin.js +14 -0
- package/src/cli.ts +31 -0
- package/{dist/index.d.ts → src/index.ts} +3 -2
- package/src/migrator/diff-calculator.ts +466 -0
- package/src/migrator/migrator.ts +576 -0
- package/src/run-cli.sh +2 -0
- package/tsconfig.json +31 -0
- package/.eslintrc.js +0 -33
- package/.prettierrc +0 -5
- package/cheetah.config.ts +0 -15
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -28
- package/dist/cli.js.map +0 -1
- package/dist/index.js +0 -19
- package/dist/index.js.map +0 -1
- package/dist/migrator/diff-calculator.d.ts +0 -20
- package/dist/migrator/diff-calculator.js +0 -339
- package/dist/migrator/diff-calculator.js.map +0 -1
- package/dist/migrator/migrator.d.ts +0 -84
- package/dist/migrator/migrator.js +0 -418
- package/dist/migrator/migrator.js.map +0 -1
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { InjectorService, LoggerService } from '@cheetah.js/core';
|
|
2
|
+
import { ConnectionSettings, Orm, OrmService, PgDriver } from '@cheetah.js/orm';
|
|
3
|
+
import { EntityStorage } from '@cheetah.js/orm';
|
|
4
|
+
import globby from 'globby';
|
|
5
|
+
import * as knex from 'knex';
|
|
6
|
+
import {
|
|
7
|
+
SnapshotTable,
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
} from '@cheetah.js/orm/driver/driver.interface';
|
|
11
|
+
import { DiffCalculator } from './diff-calculator';
|
|
12
|
+
import * as tsNode from 'ts-node';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
|
|
16
|
+
export type SnapshotTable = {
|
|
17
|
+
tableName: string;
|
|
18
|
+
schema?: string;
|
|
19
|
+
columns: ColumnsInfo[];
|
|
20
|
+
indexes: SnapshotIndexInfo[];
|
|
21
|
+
foreignKeys?: ForeignKeyInfo[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SnapshotIndexInfo = {
|
|
25
|
+
table: string;
|
|
26
|
+
indexName: string;
|
|
27
|
+
columnName: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ForeignKeyInfo = {
|
|
31
|
+
referencedTableName: string;
|
|
32
|
+
referencedColumnName: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ColumnsInfo = {
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
nullable?: boolean;
|
|
39
|
+
default?: string | null;
|
|
40
|
+
primary?: boolean;
|
|
41
|
+
unique?: boolean;
|
|
42
|
+
autoIncrement?: boolean;
|
|
43
|
+
length?: number;
|
|
44
|
+
isEnum?: boolean;
|
|
45
|
+
precision?: number;
|
|
46
|
+
scale?: number;
|
|
47
|
+
isDecimal?: boolean;
|
|
48
|
+
enumItems?: string[] | number[];
|
|
49
|
+
foreignKeys?: ForeignKeyInfo[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type SqlActionType = 'CREATE' | 'DELETE' | 'ALTER' | 'INDEX';
|
|
53
|
+
|
|
54
|
+
export type ColDiff = {
|
|
55
|
+
actionType: SqlActionType;
|
|
56
|
+
colName: string;
|
|
57
|
+
colType?: string;
|
|
58
|
+
colLength?: number;
|
|
59
|
+
indexTables?: { name: string; properties?: string[] }[];
|
|
60
|
+
colChanges?: {
|
|
61
|
+
default?: string | null;
|
|
62
|
+
primary?: boolean;
|
|
63
|
+
unique?: boolean;
|
|
64
|
+
nullable?: boolean;
|
|
65
|
+
autoIncrement?: boolean;
|
|
66
|
+
enumItems?: string[] | number[];
|
|
67
|
+
enumModified?: boolean;
|
|
68
|
+
precision?: number;
|
|
69
|
+
scale?: number;
|
|
70
|
+
foreignKeys?: ForeignKeyInfo[];
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type TableDiff = {
|
|
75
|
+
tableName: string;
|
|
76
|
+
schema?: string;
|
|
77
|
+
newTable?: boolean;
|
|
78
|
+
colDiffs: ColDiff[];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export class Migrator {
|
|
82
|
+
config: ConnectionSettings<any>;
|
|
83
|
+
orm: Orm<any>;
|
|
84
|
+
entities: EntityStorage = new EntityStorage();
|
|
85
|
+
knex: knex.Knex;
|
|
86
|
+
|
|
87
|
+
constructor() {
|
|
88
|
+
this.orm = Orm.getInstance();
|
|
89
|
+
if (this.orm === undefined)
|
|
90
|
+
this.orm = new Orm(new LoggerService(new InjectorService()));
|
|
91
|
+
|
|
92
|
+
this.entities = EntityStorage.getInstance();
|
|
93
|
+
if (this.entities === undefined) this.entities = new EntityStorage();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async startConnection(basePath: string = process.cwd()) {
|
|
97
|
+
await this.initConfigFile(basePath);
|
|
98
|
+
await this.initKnex();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
useTsNode() {
|
|
102
|
+
tsNode.register({
|
|
103
|
+
compilerOptions: {
|
|
104
|
+
module: 'CommonJS',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async initConfigFile(basePath: string) {
|
|
110
|
+
const paths = await globby(['cheetah.config.ts'], {
|
|
111
|
+
absolute: true,
|
|
112
|
+
cwd: basePath,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (paths.length === 0) {
|
|
116
|
+
throw new Error('Config file not found');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const config = await import(paths[0]);
|
|
120
|
+
this.config = config.default;
|
|
121
|
+
|
|
122
|
+
if (typeof this.config.entities === 'string') {
|
|
123
|
+
const paths = await globby(this.config.entities, {
|
|
124
|
+
absolute: true,
|
|
125
|
+
cwd: basePath,
|
|
126
|
+
});
|
|
127
|
+
for (const path of paths) {
|
|
128
|
+
console.log(`Importing entity from: ${path}`);
|
|
129
|
+
await import(path);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await this.initOrm();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async initOrm() {
|
|
137
|
+
const serv = new OrmService(this.orm, this.entities);
|
|
138
|
+
await serv.onInit(this.config);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private initKnex() {
|
|
142
|
+
this.knex = knex.default({
|
|
143
|
+
client: this.config.driver === PgDriver ? 'pg' : 'mysql',
|
|
144
|
+
connection: {
|
|
145
|
+
host: this.config.host,
|
|
146
|
+
user: this.config.username,
|
|
147
|
+
password: this.config.password,
|
|
148
|
+
database: this.config.database,
|
|
149
|
+
uri: this.config.connectionString as any,
|
|
150
|
+
},
|
|
151
|
+
debug: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async run(diff: TableDiff[]) {
|
|
156
|
+
const sql = [];
|
|
157
|
+
|
|
158
|
+
for (const tableDiff of diff) {
|
|
159
|
+
let query;
|
|
160
|
+
|
|
161
|
+
if (tableDiff.newTable) {
|
|
162
|
+
query = await this.createTable(tableDiff);
|
|
163
|
+
} else {
|
|
164
|
+
query = this.knex.schema
|
|
165
|
+
.withSchema(tableDiff.schema)
|
|
166
|
+
.table(tableDiff.tableName, (builder) => {
|
|
167
|
+
for (const colDiff of tableDiff.colDiffs) {
|
|
168
|
+
if (colDiff.actionType === 'INDEX') {
|
|
169
|
+
colDiff.indexTables.forEach((indexTable) => {
|
|
170
|
+
if (typeof indexTable.properties === 'undefined') {
|
|
171
|
+
builder.dropIndex([], indexTable.name);
|
|
172
|
+
} else {
|
|
173
|
+
builder.index(indexTable.properties, indexTable.name);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (colDiff.actionType === 'DELETE') {
|
|
179
|
+
builder.dropColumn(colDiff.colName);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
colDiff.actionType === 'ALTER' &&
|
|
185
|
+
!colDiff.colType &&
|
|
186
|
+
typeof colDiff.colChanges === 'undefined'
|
|
187
|
+
)
|
|
188
|
+
continue;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Change unique
|
|
192
|
+
*
|
|
193
|
+
*/
|
|
194
|
+
if (typeof colDiff.colChanges?.unique !== 'undefined') {
|
|
195
|
+
if (colDiff.colChanges?.unique) {
|
|
196
|
+
builder.unique(
|
|
197
|
+
[colDiff.colName],
|
|
198
|
+
`${tableDiff.tableName}_${colDiff.colName}_key`,
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
builder.dropUnique(
|
|
202
|
+
[colDiff.colName],
|
|
203
|
+
`${tableDiff.tableName}_${colDiff.colName}_key`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Change Fks
|
|
210
|
+
*
|
|
211
|
+
*/
|
|
212
|
+
if (typeof colDiff.colChanges?.foreignKeys !== 'undefined') {
|
|
213
|
+
if (colDiff.colChanges.foreignKeys.length !== 0) {
|
|
214
|
+
colDiff.colChanges.foreignKeys.forEach((fk) => {
|
|
215
|
+
builder
|
|
216
|
+
.foreign(
|
|
217
|
+
colDiff.colName,
|
|
218
|
+
`${tableDiff.tableName}_${colDiff.colName}_fk`,
|
|
219
|
+
)
|
|
220
|
+
.references(
|
|
221
|
+
`${fk.referencedTableName}.${fk.referencedColumnName}`,
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
} else {
|
|
225
|
+
builder.dropForeign(
|
|
226
|
+
colDiff.colName,
|
|
227
|
+
`${tableDiff.tableName}_${colDiff.colName}_fk`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const columnBuilder = this.assignType(builder, colDiff, tableDiff);
|
|
233
|
+
|
|
234
|
+
if (!columnBuilder) continue;
|
|
235
|
+
|
|
236
|
+
if (colDiff.actionType === 'ALTER') {
|
|
237
|
+
columnBuilder.alter({ alterNullable: false });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
.toSQL();
|
|
243
|
+
}
|
|
244
|
+
sql.push(...query.flatMap((q) => q.sql.concat(';')));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return sql;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private async createTable(tableDiff: TableDiff) {
|
|
251
|
+
return await this.knex.schema
|
|
252
|
+
.withSchema(tableDiff.schema)
|
|
253
|
+
.createTable(tableDiff.tableName, (builder) => {
|
|
254
|
+
for (const diff of tableDiff.colDiffs) {
|
|
255
|
+
if (diff.actionType === 'INDEX') {
|
|
256
|
+
diff.indexTables.forEach((indexTable) => {
|
|
257
|
+
if (typeof indexTable.properties === 'undefined') {
|
|
258
|
+
builder.dropIndex([], indexTable.name);
|
|
259
|
+
} else {
|
|
260
|
+
if (indexTable.name.includes('pkey')) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
builder.index(indexTable.properties, indexTable.name);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Change Fks
|
|
270
|
+
*
|
|
271
|
+
*/
|
|
272
|
+
if (typeof diff.colChanges?.foreignKeys !== 'undefined') {
|
|
273
|
+
if (diff.colChanges.foreignKeys.length !== 0) {
|
|
274
|
+
diff.colChanges.foreignKeys.forEach((fk) => {
|
|
275
|
+
builder
|
|
276
|
+
.foreign(diff.colName, `${tableDiff.tableName}_${diff.colName}_fk`)
|
|
277
|
+
.references(
|
|
278
|
+
`${fk.referencedTableName}.${fk.referencedColumnName}`,
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
builder.dropForeign(
|
|
283
|
+
diff.colName,
|
|
284
|
+
`${tableDiff.tableName}_${diff.colName}_fk`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.assignType(builder, diff, tableDiff);
|
|
290
|
+
|
|
291
|
+
if (typeof diff.colChanges?.unique !== 'undefined') {
|
|
292
|
+
if (diff.colChanges?.unique) {
|
|
293
|
+
builder.unique(
|
|
294
|
+
[diff.colName],
|
|
295
|
+
`${tableDiff.tableName}_${diff.colName}_key`,
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
builder.dropUnique(
|
|
299
|
+
[diff.colName],
|
|
300
|
+
`${tableDiff.tableName}_${diff.colName}_key`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
.toSQL();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
assignType(
|
|
310
|
+
builder: knex.Knex.AlterTableBuilder,
|
|
311
|
+
diff: ColDiff,
|
|
312
|
+
tableDiff: TableDiff,
|
|
313
|
+
): knex.Knex.ColumnBuilder {
|
|
314
|
+
if (diff.actionType === 'ALTER') {
|
|
315
|
+
if (diff.colChanges?.nullable !== undefined) {
|
|
316
|
+
if (diff.colChanges?.nullable) {
|
|
317
|
+
builder.setNullable(diff.colName);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!diff.colType) return;
|
|
323
|
+
const columnName = diff.colName;
|
|
324
|
+
const columnType = diff.colType;
|
|
325
|
+
let columnBuilder: knex.Knex.ColumnBuilder;
|
|
326
|
+
|
|
327
|
+
if (diff.colChanges?.autoIncrement !== undefined) {
|
|
328
|
+
if (diff.colChanges?.autoIncrement) {
|
|
329
|
+
columnBuilder = builder.increments(diff.colName, {
|
|
330
|
+
primaryKey: diff.colChanges?.primary,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
switch (columnType) {
|
|
335
|
+
case 'varchar':
|
|
336
|
+
columnBuilder = builder.string(columnName, diff.colLength);
|
|
337
|
+
break;
|
|
338
|
+
case 'text':
|
|
339
|
+
columnBuilder = builder.text(columnName);
|
|
340
|
+
break;
|
|
341
|
+
case 'int':
|
|
342
|
+
case 'numeric':
|
|
343
|
+
case 'integer':
|
|
344
|
+
columnBuilder = builder.integer(columnName, diff.colLength);
|
|
345
|
+
break;
|
|
346
|
+
case 'bigint':
|
|
347
|
+
columnBuilder = builder.bigInteger(columnName);
|
|
348
|
+
break;
|
|
349
|
+
case 'double':
|
|
350
|
+
case 'float4':
|
|
351
|
+
case 'float':
|
|
352
|
+
case 'decimal':
|
|
353
|
+
columnBuilder = builder.decimal(
|
|
354
|
+
columnName,
|
|
355
|
+
diff.colChanges?.precision,
|
|
356
|
+
diff.colChanges?.scale,
|
|
357
|
+
);
|
|
358
|
+
break;
|
|
359
|
+
case 'date':
|
|
360
|
+
columnBuilder = builder.date(columnName);
|
|
361
|
+
break;
|
|
362
|
+
case 'datetime':
|
|
363
|
+
columnBuilder = builder.datetime(columnName);
|
|
364
|
+
break;
|
|
365
|
+
case 'time':
|
|
366
|
+
columnBuilder = builder.time(columnName);
|
|
367
|
+
break;
|
|
368
|
+
case 'timestamp':
|
|
369
|
+
columnBuilder = builder.timestamp(columnName, {
|
|
370
|
+
precision: diff.colLength,
|
|
371
|
+
});
|
|
372
|
+
break;
|
|
373
|
+
case 'boolean':
|
|
374
|
+
columnBuilder = builder.boolean(columnName);
|
|
375
|
+
break;
|
|
376
|
+
case 'json':
|
|
377
|
+
columnBuilder = builder.json(columnName);
|
|
378
|
+
break;
|
|
379
|
+
case 'jsonb':
|
|
380
|
+
columnBuilder = builder.jsonb(columnName);
|
|
381
|
+
break;
|
|
382
|
+
case 'enum':
|
|
383
|
+
if (diff.actionType === 'ALTER' && diff.colChanges?.enumItems) {
|
|
384
|
+
columnBuilder = builder
|
|
385
|
+
.text(columnName)
|
|
386
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
387
|
+
//@ts-ignore
|
|
388
|
+
.checkIn(diff.colChanges.enumItems ?? [], `${columnName}_check`);
|
|
389
|
+
} else {
|
|
390
|
+
columnBuilder = builder.enum(
|
|
391
|
+
columnName,
|
|
392
|
+
diff.colChanges.enumItems ?? [],
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
case 'array':
|
|
397
|
+
columnBuilder = builder.specificType(columnName, 'text[]');
|
|
398
|
+
break;
|
|
399
|
+
case 'uuid':
|
|
400
|
+
columnBuilder = builder.uuid(columnName);
|
|
401
|
+
break;
|
|
402
|
+
default:
|
|
403
|
+
columnBuilder = builder.string(columnName, diff.colLength);
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** DEFAULTS */
|
|
408
|
+
columnBuilder.notNullable();
|
|
409
|
+
|
|
410
|
+
if (diff.colChanges?.nullable !== undefined) {
|
|
411
|
+
if (diff.colChanges?.nullable) {
|
|
412
|
+
columnBuilder.nullable();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (diff.colChanges?.default) {
|
|
417
|
+
columnBuilder.defaultTo(diff.colChanges?.default);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (typeof diff.colChanges?.primary !== 'undefined') {
|
|
421
|
+
if (diff.colChanges?.primary) {
|
|
422
|
+
columnBuilder.primary();
|
|
423
|
+
} else {
|
|
424
|
+
builder.dropPrimary();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return columnBuilder;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async migrate() {
|
|
432
|
+
await this.startConnection();
|
|
433
|
+
const migrationTable = 'cheetah_migrations';
|
|
434
|
+
const migrationDirectory = path.join(
|
|
435
|
+
process.cwd(),
|
|
436
|
+
this.config.migrationPath ?? 'database/migrations',
|
|
437
|
+
);
|
|
438
|
+
const migrationFiles = fs
|
|
439
|
+
.readdirSync(migrationDirectory)
|
|
440
|
+
.filter((file) => file.endsWith('.sql'))
|
|
441
|
+
.sort();
|
|
442
|
+
|
|
443
|
+
if (migrationFiles.length === 0) {
|
|
444
|
+
this.orm.logger.info('No migration files found');
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
this.orm.driverInstance.executeSql(
|
|
449
|
+
`CREATE TABLE IF NOT EXISTS "${migrationTable}" ("migration_file" character varying(255) NOT NULL PRIMARY KEY UNIQUE);`,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const migrated = await this.orm.driverInstance.executeSql(
|
|
453
|
+
`SELECT * FROM "${migrationTable}" ORDER BY "migration_file" ASC;`,
|
|
454
|
+
);
|
|
455
|
+
const lastMigration = migrated.rows[migrated.rows.length - 1];
|
|
456
|
+
const lastMigrationIndex = migrationFiles.indexOf(
|
|
457
|
+
lastMigration?.migration_file ?? '',
|
|
458
|
+
);
|
|
459
|
+
const migrationsToExecute = migrationFiles.slice(lastMigrationIndex + 1);
|
|
460
|
+
|
|
461
|
+
if (migrationsToExecute.length === 0) {
|
|
462
|
+
this.orm.logger.info('Database is up to date');
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (const migrationFile of migrationsToExecute) {
|
|
467
|
+
const migrationFilePath = path.join(migrationDirectory, migrationFile);
|
|
468
|
+
const migrationContent = fs.readFileSync(migrationFilePath, {
|
|
469
|
+
encoding: 'utf-8',
|
|
470
|
+
});
|
|
471
|
+
const sqlInstructions = migrationContent
|
|
472
|
+
.split(';')
|
|
473
|
+
.filter((sql) => sql.trim().length > 0);
|
|
474
|
+
|
|
475
|
+
for (const sqlInstruction of sqlInstructions) {
|
|
476
|
+
await this.orm.driverInstance.executeSql(sqlInstruction);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
await this.orm.driverInstance.executeSql(
|
|
480
|
+
`INSERT INTO "${migrationTable}" ("migration_file") VALUES ('${migrationFile}');`,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
this.orm.logger.info(`Migration executed: ${migrationFile}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async generateMigration(
|
|
488
|
+
configFile: string = process.cwd(),
|
|
489
|
+
onlySql: boolean = false,
|
|
490
|
+
) {
|
|
491
|
+
await this.startConnection(configFile);
|
|
492
|
+
const snapshotBd = await this.snapshotBd();
|
|
493
|
+
const snapshotEntities = await this.snapshotEntities();
|
|
494
|
+
const calculator = new DiffCalculator(this.entities, this.orm.driverInstance);
|
|
495
|
+
const diff = calculator.diff(snapshotBd, snapshotEntities);
|
|
496
|
+
|
|
497
|
+
const sql = this.lastTreatment(await this.run(diff));
|
|
498
|
+
|
|
499
|
+
if (onlySql) {
|
|
500
|
+
return sql;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// save sql in file
|
|
504
|
+
const fs = await import('fs');
|
|
505
|
+
const path = await import('path');
|
|
506
|
+
const directory = path.join(
|
|
507
|
+
process.cwd(),
|
|
508
|
+
this.config.migrationPath ?? 'database/migrations',
|
|
509
|
+
);
|
|
510
|
+
const fileName =
|
|
511
|
+
`migration_${new Date().toISOString().replace(/[^\d]/g, '')}` + `.sql`;
|
|
512
|
+
const migrationFilePath = path.join(directory, fileName);
|
|
513
|
+
if (!fs.existsSync(directory)) {
|
|
514
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const migrationContent = sql.join('\n');
|
|
518
|
+
|
|
519
|
+
if (migrationContent.length === 0) {
|
|
520
|
+
this.orm.logger.info('No changes detected');
|
|
521
|
+
return;
|
|
522
|
+
} else {
|
|
523
|
+
fs.writeFileSync(migrationFilePath, migrationContent);
|
|
524
|
+
this.orm.logger.info(`Migration file created: ${migrationFilePath}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private lastTreatment(sql: any[]) {
|
|
529
|
+
const getDropIndexes = sql.filter((s) => s.startsWith('drop index'));
|
|
530
|
+
|
|
531
|
+
// Drop indexes not execute if the column is deleted
|
|
532
|
+
const getDropColumns = sql.filter((s) => s.includes('drop column'));
|
|
533
|
+
const dropIndexes = [];
|
|
534
|
+
for (const dropIndex of getDropIndexes) {
|
|
535
|
+
const indexName = dropIndex.split(' ')[2].split('_');
|
|
536
|
+
let colName;
|
|
537
|
+
|
|
538
|
+
if (indexName.length === 3) {
|
|
539
|
+
colName = indexName[1];
|
|
540
|
+
} else {
|
|
541
|
+
colName = indexName[0];
|
|
542
|
+
}
|
|
543
|
+
colName = colName.split('.')[1].replace(/"/g, '');
|
|
544
|
+
|
|
545
|
+
const dropColumn = getDropColumns.find((s) => s.includes(colName));
|
|
546
|
+
|
|
547
|
+
if (dropColumn) {
|
|
548
|
+
dropIndexes.push(dropIndex);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const sqlFiltered = sql.filter((s) => !dropIndexes.includes(s));
|
|
553
|
+
|
|
554
|
+
return sqlFiltered;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private async snapshotBd(): Promise<SnapshotTable[]> {
|
|
558
|
+
const snapshot = [];
|
|
559
|
+
for (const [_, values] of this.entities.entries()) {
|
|
560
|
+
const bd = await this.orm.driverInstance.snapshot(values.tableName);
|
|
561
|
+
if (!bd) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
snapshot.push(bd);
|
|
565
|
+
}
|
|
566
|
+
return snapshot;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private async snapshotEntities() {
|
|
570
|
+
const snapshot = [];
|
|
571
|
+
for (const [_, values] of this.entities.entries()) {
|
|
572
|
+
snapshot.push(await this.entities.snapshot(values));
|
|
573
|
+
}
|
|
574
|
+
return snapshot;
|
|
575
|
+
}
|
|
576
|
+
}
|
package/src/run-cli.sh
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": [
|
|
3
|
+
"src/**/*",
|
|
4
|
+
"src/run-cli.sh"
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"module": "commonjs",
|
|
8
|
+
"moduleResolution": "node",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"removeComments": true,
|
|
11
|
+
"emitDecoratorMetadata": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"experimentalDecorators": true,
|
|
14
|
+
"allowSyntheticDefaultImports": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"target": "ES2021",
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"outDir": "./dist",
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"incremental": true,
|
|
21
|
+
"skipLibCheck": true,
|
|
22
|
+
"strictNullChecks": false,
|
|
23
|
+
"noImplicitAny": false,
|
|
24
|
+
"strictBindCallApply": false,
|
|
25
|
+
"forceConsistentCasingInFileNames": false,
|
|
26
|
+
"noFallthroughCasesInSwitch": false,
|
|
27
|
+
"types": [
|
|
28
|
+
"bun-types"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
package/.eslintrc.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
parser: '@typescript-eslint/parser',
|
|
3
|
-
parserOptions: {
|
|
4
|
-
project: 'tsconfig.json',
|
|
5
|
-
tsconfigRootDir: __dirname,
|
|
6
|
-
sourceType: 'module',
|
|
7
|
-
},
|
|
8
|
-
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
9
|
-
extends: [
|
|
10
|
-
'plugin:@typescript-eslint/recommended',
|
|
11
|
-
'plugin:prettier/recommended',
|
|
12
|
-
],
|
|
13
|
-
root: true,
|
|
14
|
-
env: {
|
|
15
|
-
node: true,
|
|
16
|
-
jest: true,
|
|
17
|
-
},
|
|
18
|
-
ignorePatterns: ['.eslintrc.js'],
|
|
19
|
-
rules: {
|
|
20
|
-
'@typescript-eslint/interface-name-prefix': 'off',
|
|
21
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
22
|
-
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
23
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
-
"@typescript-eslint/no-unused-vars": [
|
|
25
|
-
"warn", // or "error"
|
|
26
|
-
{
|
|
27
|
-
"argsIgnorePattern": "^_",
|
|
28
|
-
"varsIgnorePattern": "^_",
|
|
29
|
-
"caughtErrorsIgnorePattern": "^_"
|
|
30
|
-
}
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
};
|
package/.prettierrc
DELETED
package/cheetah.config.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ConnectionSettings, PgDriver } from '@cheetah.js/orm';
|
|
2
|
-
|
|
3
|
-
const config: ConnectionSettings = {
|
|
4
|
-
host: 'localhost',
|
|
5
|
-
port: 5432,
|
|
6
|
-
database: 'postgres',
|
|
7
|
-
username: 'postgres',
|
|
8
|
-
password: 'postgres',
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
10
|
-
// @ts-ignore
|
|
11
|
-
driver: PgDriver,
|
|
12
|
-
migrationPath: '/database/migration',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export default config;
|
package/dist/cli.d.ts
DELETED
package/dist/cli.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
require("reflect-metadata");
|
|
5
|
-
const commander_1 = require("commander");
|
|
6
|
-
const migrator_1 = require("./migrator/migrator");
|
|
7
|
-
const program = new commander_1.Command();
|
|
8
|
-
program.name('[npx|bunx] cli').description('CLI to Cheetah.js ORM ');
|
|
9
|
-
program
|
|
10
|
-
.command('migration:generate')
|
|
11
|
-
.description('generate a new migration file with a diff')
|
|
12
|
-
.action(async (str, options) => {
|
|
13
|
-
const migrator = new migrator_1.Migrator();
|
|
14
|
-
migrator.useTsNode();
|
|
15
|
-
await migrator.generateMigration();
|
|
16
|
-
process.exit(0);
|
|
17
|
-
});
|
|
18
|
-
program
|
|
19
|
-
.command('migration:run')
|
|
20
|
-
.description('run all pending migrations')
|
|
21
|
-
.action(async (str, options) => {
|
|
22
|
-
const migrator = new migrator_1.Migrator();
|
|
23
|
-
migrator.useTsNode();
|
|
24
|
-
await migrator.migrate();
|
|
25
|
-
process.exit(0);
|
|
26
|
-
});
|
|
27
|
-
program.parse();
|
|
28
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAEA,4BAA0B;AAC1B,yCAAoC;AACpC,kDAA+C;AAE/C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;AAErE,OAAO;KACJ,OAAO,CAAC,oBAAoB,CAAC;KAC7B,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IAC7B,MAAM,QAAQ,GAAG,IAAI,mBAAQ,EAAE,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,CAAC;IACrB,MAAM,QAAQ,CAAC,iBAAiB,EAAE,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,4BAA4B,CAAC;KACzC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IAC7B,MAAM,QAAQ,GAAG,IAAI,mBAAQ,EAAE,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,CAAC;IACrB,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;IACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|