sonamu 0.0.41 → 0.1.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/.pnp.cjs +1968 -956
- package/.pnp.loader.mjs +1816 -54
- package/.yarnrc.yml +1 -1
- package/dist/api/caster.d.ts +2 -4
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/code-converters.d.ts +3 -3
- package/dist/api/code-converters.d.ts.map +1 -1
- package/dist/api/code-converters.js +5 -15
- package/dist/api/code-converters.js.map +1 -1
- package/dist/api/context.d.ts +1 -1
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/decorators.d.ts +3 -3
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/sonamu.d.ts +3 -3
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +6 -6
- package/dist/api/sonamu.js.map +1 -1
- package/dist/bin/cli.js +240 -26
- package/dist/bin/cli.js.map +1 -1
- package/dist/database/db.d.ts +2 -2
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +1 -1
- package/dist/database/db.js.map +1 -1
- package/dist/database/upsert-builder.d.ts +2 -2
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +10 -8
- package/dist/database/upsert-builder.js.map +1 -1
- package/dist/entity/entity-manager.d.ts +29 -0
- package/dist/entity/entity-manager.d.ts.map +1 -0
- package/dist/entity/entity-manager.js +128 -0
- package/dist/entity/entity-manager.js.map +1 -0
- package/dist/entity/entity-utils.d.ts +61 -0
- package/dist/entity/entity-utils.d.ts.map +1 -0
- package/dist/entity/entity-utils.js +121 -0
- package/dist/entity/entity-utils.js.map +1 -0
- package/dist/entity/entity.d.ts +49 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entity.js +504 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/migrator.d.ts +143 -0
- package/dist/entity/migrator.d.ts.map +1 -0
- package/dist/entity/migrator.js +1385 -0
- package/dist/entity/migrator.js.map +1 -0
- package/dist/entity/smd-utils.d.ts +61 -0
- package/dist/entity/smd-utils.d.ts.map +1 -0
- package/dist/entity/smd-utils.js +121 -0
- package/dist/entity/smd-utils.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/smd/entity-manager.d.ts +28 -0
- package/dist/smd/entity-manager.d.ts.map +1 -0
- package/dist/smd/entity-manager.js +119 -0
- package/dist/smd/entity-manager.js.map +1 -0
- package/dist/smd/entity.d.ts +40 -0
- package/dist/smd/entity.d.ts.map +1 -0
- package/dist/smd/entity.js +430 -0
- package/dist/smd/entity.js.map +1 -0
- package/dist/smd/migrator.d.ts +2 -2
- package/dist/smd/migrator.d.ts.map +1 -1
- package/dist/smd/migrator.js +5 -5
- package/dist/smd/migrator.js.map +1 -1
- package/dist/smd/smd-manager.d.ts +3 -3
- package/dist/smd/smd-manager.d.ts.map +1 -1
- package/dist/smd/smd-manager.js +2 -2
- package/dist/smd/smd-manager.js.map +1 -1
- package/dist/smd/smd-utils.d.ts +4 -4
- package/dist/smd/smd-utils.d.ts.map +1 -1
- package/dist/smd/smd-utils.js.map +1 -1
- package/dist/smd/smd.d.ts +5 -6
- package/dist/smd/smd.d.ts.map +1 -1
- package/dist/smd/smd.js +3 -3
- package/dist/smd/smd.js.map +1 -1
- package/dist/syncer/syncer.d.ts +15 -11
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +134 -74
- package/dist/syncer/syncer.js.map +1 -1
- package/dist/templates/base-template.d.ts +2 -2
- package/dist/templates/base-template.d.ts.map +1 -1
- package/dist/templates/entity.template.d.ts +17 -0
- package/dist/templates/entity.template.d.ts.map +1 -0
- package/dist/templates/entity.template.js +46 -0
- package/dist/templates/entity.template.js.map +1 -0
- package/dist/templates/generated.template.d.ts +11 -7
- package/dist/templates/generated.template.d.ts.map +1 -1
- package/dist/templates/generated.template.js +72 -43
- package/dist/templates/generated.template.js.map +1 -1
- package/dist/templates/generated_http.template.d.ts +3 -3
- package/dist/templates/generated_http.template.d.ts.map +1 -1
- package/dist/templates/generated_http.template.js +3 -3
- package/dist/templates/generated_http.template.js.map +1 -1
- package/dist/templates/init_enums.template.d.ts +2 -2
- package/dist/templates/init_enums.template.d.ts.map +1 -1
- package/dist/templates/init_enums.template.js +2 -2
- package/dist/templates/init_enums.template.js.map +1 -1
- package/dist/templates/init_generated.template.d.ts +3 -3
- package/dist/templates/init_generated.template.d.ts.map +1 -1
- package/dist/templates/init_generated.template.js +13 -14
- package/dist/templates/init_generated.template.js.map +1 -1
- package/dist/templates/init_types.template.d.ts +3 -3
- package/dist/templates/init_types.template.d.ts.map +1 -1
- package/dist/templates/init_types.template.js +10 -10
- package/dist/templates/init_types.template.js.map +1 -1
- package/dist/templates/model.template.d.ts +3 -3
- package/dist/templates/model.template.d.ts.map +1 -1
- package/dist/templates/model.template.js +28 -28
- package/dist/templates/model.template.js.map +1 -1
- package/dist/templates/model_test.template.d.ts +3 -3
- package/dist/templates/model_test.template.d.ts.map +1 -1
- package/dist/templates/model_test.template.js +4 -4
- package/dist/templates/model_test.template.js.map +1 -1
- package/dist/templates/service.template.d.ts +3 -3
- package/dist/templates/service.template.d.ts.map +1 -1
- package/dist/templates/service.template.js +3 -3
- package/dist/templates/service.template.js.map +1 -1
- package/dist/templates/smd.template.d.ts +2 -2
- package/dist/templates/smd.template.d.ts.map +1 -1
- package/dist/templates/smd.template.js +2 -2
- package/dist/templates/smd.template.js.map +1 -1
- package/dist/templates/view_enums_buttonset.template.d.ts +3 -3
- package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -1
- package/dist/templates/view_enums_buttonset.template.js +4 -4
- package/dist/templates/view_enums_buttonset.template.js.map +1 -1
- package/dist/templates/view_enums_dropdown.template.d.ts +3 -3
- package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -1
- package/dist/templates/view_enums_dropdown.template.js +3 -3
- package/dist/templates/view_enums_dropdown.template.js.map +1 -1
- package/dist/templates/view_enums_select.template.d.ts +3 -3
- package/dist/templates/view_enums_select.template.d.ts.map +1 -1
- package/dist/templates/view_enums_select.template.js +3 -3
- package/dist/templates/view_enums_select.template.js.map +1 -1
- package/dist/templates/view_form.template.d.ts +25 -29
- package/dist/templates/view_form.template.d.ts.map +1 -1
- package/dist/templates/view_form.template.js +19 -19
- package/dist/templates/view_form.template.js.map +1 -1
- package/dist/templates/view_id_all_select.template.d.ts +3 -3
- package/dist/templates/view_id_all_select.template.d.ts.map +1 -1
- package/dist/templates/view_id_all_select.template.js +4 -4
- package/dist/templates/view_id_all_select.template.js.map +1 -1
- package/dist/templates/view_id_async_select.template.d.ts +3 -3
- package/dist/templates/view_id_async_select.template.d.ts.map +1 -1
- package/dist/templates/view_id_async_select.template.js +6 -6
- package/dist/templates/view_id_async_select.template.js.map +1 -1
- package/dist/templates/view_list.template.d.ts +30 -34
- package/dist/templates/view_list.template.d.ts.map +1 -1
- package/dist/templates/view_list.template.js +40 -40
- package/dist/templates/view_list.template.js.map +1 -1
- package/dist/templates/view_list_columns.template.d.ts +3 -3
- package/dist/templates/view_list_columns.template.d.ts.map +1 -1
- package/dist/templates/view_list_columns.template.js +3 -3
- package/dist/templates/view_list_columns.template.js.map +1 -1
- package/dist/templates/view_search_input.template.d.ts +3 -3
- package/dist/templates/view_search_input.template.d.ts.map +1 -1
- package/dist/templates/view_search_input.template.js +3 -3
- package/dist/templates/view_search_input.template.js.map +1 -1
- package/dist/testing/fixture-manager.d.ts +2 -3
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +21 -42
- package/dist/testing/fixture-manager.js.map +1 -1
- package/dist/types/smd.types.d.ts +741 -0
- package/dist/types/smd.types.d.ts.map +1 -0
- package/dist/types/smd.types.js +292 -0
- package/dist/types/smd.types.js.map +1 -0
- package/dist/types/types.d.ts +185 -190
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +22 -30
- package/dist/types/types.js.map +1 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +14 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/main.d.ts +4 -0
- package/dist/ui/main.d.ts.map +1 -0
- package/dist/ui/main.js +15 -0
- package/dist/ui/main.js.map +1 -0
- package/dist/utils/model.d.ts +2 -2
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/utils.d.ts +1 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +6 -2
- package/dist/utils/utils.js.map +1 -1
- package/package.json +11 -8
- package/src/api/code-converters.ts +9 -17
- package/src/api/sonamu.ts +10 -6
- package/src/bin/cli.ts +247 -26
- package/src/database/upsert-builder.ts +2 -2
- package/src/entity/entity-manager.ts +150 -0
- package/src/{smd/smd-utils.ts → entity/entity-utils.ts} +4 -7
- package/src/entity/entity.ts +666 -0
- package/src/{smd → entity}/migrator.ts +426 -106
- package/src/index.ts +3 -3
- package/src/smd/smd-manager.ts +3 -13
- package/src/smd/smd.ts +13 -10
- package/src/syncer/syncer.ts +125 -73
- package/src/templates/base-template.ts +2 -2
- package/src/templates/entity.template.ts +50 -0
- package/src/templates/generated.template.ts +93 -57
- package/src/templates/generated_http.template.ts +4 -4
- package/src/templates/init_types.template.ts +11 -11
- package/src/templates/model.template.ts +29 -29
- package/src/templates/model_test.template.ts +5 -5
- package/src/templates/service.template.ts +4 -4
- package/src/templates/view_enums_buttonset.template.ts +5 -5
- package/src/templates/view_enums_dropdown.template.ts +4 -4
- package/src/templates/view_enums_select.template.ts +8 -4
- package/src/templates/view_form.template.ts +21 -21
- package/src/templates/view_id_all_select.template.ts +5 -5
- package/src/templates/view_id_async_select.template.ts +9 -7
- package/src/templates/view_list.template.ts +54 -44
- package/src/templates/view_list_columns.template.ts +4 -4
- package/src/templates/view_search_input.template.ts +4 -4
- package/src/testing/fixture-manager.ts +19 -60
- package/src/types/types.ts +59 -41
- package/src/utils/utils.ts +4 -0
- package/tsconfig.json +4 -1
- package/src/templates/init_enums.template.ts +0 -71
- package/src/templates/init_generated.template.ts +0 -51
- package/src/templates/smd.template.ts +0 -53
|
@@ -0,0 +1,1385 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Migrator = void 0;
|
|
16
|
+
const lodash_1 = require("lodash");
|
|
17
|
+
const knex_1 = __importDefault(require("knex"));
|
|
18
|
+
const prettier_1 = __importDefault(require("prettier"));
|
|
19
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
20
|
+
const luxon_1 = require("luxon");
|
|
21
|
+
const fs_1 = require("fs");
|
|
22
|
+
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
|
|
23
|
+
const inflection_1 = require("inflection");
|
|
24
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
25
|
+
const child_process_1 = require("child_process");
|
|
26
|
+
const path_1 = __importDefault(require("path"));
|
|
27
|
+
const types_1 = require("../types/types");
|
|
28
|
+
const lodash_able_1 = require("../utils/lodash-able");
|
|
29
|
+
const entity_manager_1 = require("./entity-manager");
|
|
30
|
+
const api_1 = require("../api");
|
|
31
|
+
const so_exceptions_1 = require("../exceptions/so-exceptions");
|
|
32
|
+
const utils_1 = require("../utils/utils");
|
|
33
|
+
class Migrator {
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.mode = options.mode;
|
|
36
|
+
const { dbConfig } = api_1.Sonamu;
|
|
37
|
+
if (this.mode === "dev") {
|
|
38
|
+
const devDB = (0, knex_1.default)(dbConfig.development_master);
|
|
39
|
+
const testDB = (0, knex_1.default)(dbConfig.test);
|
|
40
|
+
const fixtureLocalDB = (0, knex_1.default)(dbConfig.fixture_local);
|
|
41
|
+
const applyDBs = [devDB, testDB, fixtureLocalDB];
|
|
42
|
+
if (dbConfig.fixture_local.connection
|
|
43
|
+
.host !==
|
|
44
|
+
dbConfig.fixture_remote.connection.host) {
|
|
45
|
+
const fixtureRemoteDB = (0, knex_1.default)(dbConfig.fixture_remote);
|
|
46
|
+
applyDBs.push(fixtureRemoteDB);
|
|
47
|
+
}
|
|
48
|
+
this.targets = {
|
|
49
|
+
compare: devDB,
|
|
50
|
+
pending: devDB,
|
|
51
|
+
shadow: testDB,
|
|
52
|
+
apply: applyDBs,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
else if (this.mode === "deploy") {
|
|
56
|
+
const productionDB = (0, knex_1.default)(api_1.Sonamu.dbConfig.production_master);
|
|
57
|
+
const testDB = (0, knex_1.default)(api_1.Sonamu.dbConfig.test);
|
|
58
|
+
this.targets = {
|
|
59
|
+
pending: productionDB,
|
|
60
|
+
shadow: testDB,
|
|
61
|
+
apply: [productionDB],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw new Error(`잘못된 모드 ${this.mode} 입력`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
getMigrationCodes() {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const srcMigrationsDir = `${api_1.Sonamu.apiRootPath}/src/migrations`;
|
|
71
|
+
const distMigrationsDir = `${api_1.Sonamu.apiRootPath}/dist/migrations`;
|
|
72
|
+
const srcMigrations = (0, fs_1.readdirSync)(srcMigrationsDir)
|
|
73
|
+
.filter((f) => f.endsWith(".ts"))
|
|
74
|
+
.map((f) => f.split(".")[0]);
|
|
75
|
+
const distMigrations = (0, fs_1.readdirSync)(distMigrationsDir)
|
|
76
|
+
.filter((f) => f.endsWith(".js"))
|
|
77
|
+
.map((f) => f.split(".")[0]);
|
|
78
|
+
const normal = (0, lodash_1.intersection)(srcMigrations, distMigrations)
|
|
79
|
+
.map((filename) => {
|
|
80
|
+
return {
|
|
81
|
+
name: filename,
|
|
82
|
+
path: path_1.default.join(srcMigrationsDir, filename) + ".ts",
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
.sort((a, b) => (a > b ? 1 : -1));
|
|
86
|
+
const onlyTs = (0, lodash_1.difference)(srcMigrations, distMigrations).map((filename) => {
|
|
87
|
+
return {
|
|
88
|
+
name: filename,
|
|
89
|
+
path: path_1.default.join(srcMigrationsDir, filename) + ".ts",
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
const onlyJs = (0, lodash_1.difference)(distMigrations, srcMigrations).map((filename) => {
|
|
93
|
+
return {
|
|
94
|
+
name: filename,
|
|
95
|
+
path: path_1.default.join(distMigrationsDir, filename) + ".js",
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
normal,
|
|
100
|
+
onlyTs,
|
|
101
|
+
onlyJs,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
getStatus() {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
const { normal, onlyTs, onlyJs } = yield this.getMigrationCodes();
|
|
108
|
+
if (onlyTs.length > 0) {
|
|
109
|
+
console.debug({ onlyTs });
|
|
110
|
+
throw new so_exceptions_1.ServiceUnavailableException(`There is an un-compiled TS migration files.\nPlease run the dev:serve\n\n${onlyTs
|
|
111
|
+
.map((f) => f.name)
|
|
112
|
+
.join("\n")}`);
|
|
113
|
+
}
|
|
114
|
+
if (onlyJs.length > 0) {
|
|
115
|
+
console.debug({ onlyJs });
|
|
116
|
+
yield Promise.all(onlyJs.map((f) => __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
(0, child_process_1.execSync)(`rm -f ${f.path.replace("/src/", "/dist/").replace(".ts", ".js")}`);
|
|
118
|
+
})));
|
|
119
|
+
}
|
|
120
|
+
const connKeys = Object.keys(api_1.Sonamu.dbConfig).filter((key) => key.endsWith("_slave") === false);
|
|
121
|
+
const statuses = yield Promise.all(connKeys.map((connKey) => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
var _a, _b;
|
|
123
|
+
const knexOptions = api_1.Sonamu.dbConfig[connKey];
|
|
124
|
+
const tConn = (0, knex_1.default)(knexOptions);
|
|
125
|
+
const status = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
try {
|
|
127
|
+
return tConn.migrate.status();
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return "error";
|
|
131
|
+
}
|
|
132
|
+
}))();
|
|
133
|
+
const pending = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
134
|
+
try {
|
|
135
|
+
const [, fdList] = yield tConn.migrate.list();
|
|
136
|
+
return fdList.map((fd) => fd.file.replace(".js", ""));
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}))();
|
|
142
|
+
const currentVersion = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
143
|
+
try {
|
|
144
|
+
return tConn.migrate.currentVersion();
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return "error";
|
|
148
|
+
}
|
|
149
|
+
}))();
|
|
150
|
+
const connection = knexOptions.connection;
|
|
151
|
+
yield tConn.destroy();
|
|
152
|
+
return {
|
|
153
|
+
name: connKey.replace("_master", ""),
|
|
154
|
+
connKey,
|
|
155
|
+
connString: `${knexOptions.client}://${(_a = connection.user) !== null && _a !== void 0 ? _a : ""}@${connection.host}:${(_b = connection.port) !== null && _b !== void 0 ? _b : 3306}/${connection.database}`,
|
|
156
|
+
currentVersion,
|
|
157
|
+
status,
|
|
158
|
+
pending,
|
|
159
|
+
};
|
|
160
|
+
})));
|
|
161
|
+
const preparedCodes = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
const status0conn = statuses.find((status) => status.status === 0);
|
|
163
|
+
if (status0conn === undefined) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const compareDBconn = (0, knex_1.default)(api_1.Sonamu.dbConfig[status0conn.connKey]);
|
|
167
|
+
const genCodes = yield this.compareMigrations(compareDBconn);
|
|
168
|
+
yield compareDBconn.destroy();
|
|
169
|
+
return genCodes;
|
|
170
|
+
}))();
|
|
171
|
+
return {
|
|
172
|
+
conns: statuses,
|
|
173
|
+
codes: normal,
|
|
174
|
+
preparedCodes,
|
|
175
|
+
};
|
|
176
|
+
/*
|
|
177
|
+
TS/JS 코드 컴파일 상태 확인
|
|
178
|
+
1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
|
|
179
|
+
2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
|
|
180
|
+
|
|
181
|
+
DB 마이그레이션 상태 확인
|
|
182
|
+
1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
|
|
183
|
+
- connKey: string
|
|
184
|
+
- status: number
|
|
185
|
+
- currentVersion: string
|
|
186
|
+
- list: { file: string; directory: string }[]
|
|
187
|
+
|
|
188
|
+
*/
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
runAction(action, targets) {
|
|
192
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
// get connections
|
|
194
|
+
const conns = (yield Promise.all(targets.map((target) => __awaiter(this, void 0, void 0, function* () {
|
|
195
|
+
const knexOptions = api_1.Sonamu.dbConfig[target];
|
|
196
|
+
if (knexOptions === undefined) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
connKey: target,
|
|
201
|
+
knex: (0, knex_1.default)(knexOptions),
|
|
202
|
+
};
|
|
203
|
+
})))).filter(utils_1.nonNullable);
|
|
204
|
+
// action
|
|
205
|
+
const result = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
206
|
+
switch (action) {
|
|
207
|
+
case "latest":
|
|
208
|
+
return Promise.all(conns.map(({ connKey, knex }) => __awaiter(this, void 0, void 0, function* () {
|
|
209
|
+
const [batchNo, applied] = yield knex.migrate.latest();
|
|
210
|
+
return {
|
|
211
|
+
connKey,
|
|
212
|
+
batchNo,
|
|
213
|
+
applied,
|
|
214
|
+
};
|
|
215
|
+
})));
|
|
216
|
+
case "rollback":
|
|
217
|
+
return Promise.all(conns.map(({ connKey, knex }) => __awaiter(this, void 0, void 0, function* () {
|
|
218
|
+
const [batchNo, applied] = yield knex.migrate.rollback();
|
|
219
|
+
return {
|
|
220
|
+
connKey,
|
|
221
|
+
batchNo,
|
|
222
|
+
applied,
|
|
223
|
+
};
|
|
224
|
+
})));
|
|
225
|
+
}
|
|
226
|
+
}))();
|
|
227
|
+
// destroy
|
|
228
|
+
yield Promise.all(conns.map(({ knex }) => {
|
|
229
|
+
return knex.destroy();
|
|
230
|
+
}));
|
|
231
|
+
return result;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
delCodes(codeNames) {
|
|
235
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
236
|
+
const { conns } = yield this.getStatus();
|
|
237
|
+
if (conns.some((conn) => {
|
|
238
|
+
return codeNames.some((codeName) => conn.pending.includes(codeName) === false);
|
|
239
|
+
})) {
|
|
240
|
+
throw new Error("You cannot delete a migration file if there is already applied.");
|
|
241
|
+
}
|
|
242
|
+
const delFiles = codeNames
|
|
243
|
+
.map((codeName) => [
|
|
244
|
+
`${api_1.Sonamu.apiRootPath}/src/migrations/${codeName}.ts`,
|
|
245
|
+
`${api_1.Sonamu.apiRootPath}/dist/migrations/${codeName}.js`,
|
|
246
|
+
])
|
|
247
|
+
.flat();
|
|
248
|
+
const res = yield Promise.all(delFiles.map((delFile) => {
|
|
249
|
+
if ((0, fs_1.existsSync)(delFile)) {
|
|
250
|
+
console.log(chalk_1.default.red(`DELETE: ${delFile}`));
|
|
251
|
+
(0, fs_1.unlinkSync)(delFile);
|
|
252
|
+
return delFiles.includes(".ts") ? 1 : 0;
|
|
253
|
+
}
|
|
254
|
+
return 0;
|
|
255
|
+
}));
|
|
256
|
+
return (0, lodash_1.sum)(res);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
generatePreparedCodes() {
|
|
260
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
const { preparedCodes } = yield this.getStatus();
|
|
262
|
+
if (preparedCodes.length === 0) {
|
|
263
|
+
console.log(chalk_1.default.green("\n현재 모두 싱크된 상태입니다."));
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
// 실제 코드 생성
|
|
267
|
+
const migrationsDir = `${api_1.Sonamu.apiRootPath}/src/migrations`;
|
|
268
|
+
preparedCodes
|
|
269
|
+
.filter((pcode) => pcode.formatted)
|
|
270
|
+
.map((pcode, index) => {
|
|
271
|
+
const dateTag = luxon_1.DateTime.local()
|
|
272
|
+
.plus({ seconds: index })
|
|
273
|
+
.toFormat("yyyyMMddHHmmss");
|
|
274
|
+
const filePath = `${migrationsDir}/${dateTag}_${pcode.title}.ts`;
|
|
275
|
+
(0, fs_1.writeFileSync)(filePath, pcode.formatted);
|
|
276
|
+
console.log(chalk_1.default.green(`MIGRTAION CRETATED ${filePath}`));
|
|
277
|
+
});
|
|
278
|
+
return preparedCodes.length;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
clearPendingList() {
|
|
282
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
283
|
+
const [, pendingList] = (yield this.targets.pending.migrate.list());
|
|
284
|
+
const migrationsDir = `${api_1.Sonamu.apiRootPath}/src/migrations`;
|
|
285
|
+
const delList = pendingList.map((df) => {
|
|
286
|
+
return path_1.default.join(migrationsDir, df.file).replace(".js", ".ts");
|
|
287
|
+
});
|
|
288
|
+
for (let p of delList) {
|
|
289
|
+
if ((0, fs_1.existsSync)(p)) {
|
|
290
|
+
(0, fs_1.unlinkSync)(p);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
yield this.cleanUpDist(true);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
check() {
|
|
297
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
298
|
+
const codes = yield this.compareMigrations(this.targets.compare);
|
|
299
|
+
if (codes.length === 0) {
|
|
300
|
+
console.log(chalk_1.default.green("\n현재 모두 싱크된 상태입니다."));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// 현재 생성된 코드 표기
|
|
304
|
+
console.table(codes, ["type", "title"]);
|
|
305
|
+
console.log(codes[0]);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
run() {
|
|
309
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
310
|
+
// pending 마이그레이션 확인
|
|
311
|
+
const [, pendingList] = yield this.targets.pending.migrate.list();
|
|
312
|
+
if (pendingList.length > 0) {
|
|
313
|
+
console.log(chalk_1.default.red("pending 된 마이그레이션이 존재합니다."), pendingList.map((pending) => pending.file));
|
|
314
|
+
// pending이 있는 경우 Shadow DB 테스트 진행 여부 컨펌
|
|
315
|
+
const answer = yield (0, prompts_1.default)({
|
|
316
|
+
type: "confirm",
|
|
317
|
+
name: "value",
|
|
318
|
+
message: "Shadow DB 테스트를 진행하시겠습니까?",
|
|
319
|
+
initial: true,
|
|
320
|
+
});
|
|
321
|
+
if (answer.value === false) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
console.time(chalk_1.default.blue("Migrator - runShadowTest"));
|
|
325
|
+
yield this.runShadowTest();
|
|
326
|
+
console.timeEnd(chalk_1.default.blue("Migrator - runShadowTest"));
|
|
327
|
+
yield Promise.all(this.targets.apply.map((applyDb) => __awaiter(this, void 0, void 0, function* () {
|
|
328
|
+
const label = chalk_1.default.green(`APPLIED ${applyDb.client.connectionSettings.host} ${applyDb.client.database()}`);
|
|
329
|
+
console.time(label);
|
|
330
|
+
const [,] = yield applyDb.migrate.latest();
|
|
331
|
+
console.timeEnd(label);
|
|
332
|
+
})));
|
|
333
|
+
}
|
|
334
|
+
// MD-DB간 비교하여 코드 생성 리턴
|
|
335
|
+
const codes = yield this.compareMigrations(this.targets.compare);
|
|
336
|
+
if (codes.length === 0) {
|
|
337
|
+
console.log(chalk_1.default.green("\n현재 모두 싱크된 상태입니다."));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// 현재 생성된 코드 표기
|
|
341
|
+
console.table(codes, ["type", "title"]);
|
|
342
|
+
/* DEBUG: 디버깅용 코드
|
|
343
|
+
codes.map((code) => console.log(code.formatted));
|
|
344
|
+
process.exit();
|
|
345
|
+
*/
|
|
346
|
+
// 실제 파일 생성 프롬프트
|
|
347
|
+
const answer = yield (0, prompts_1.default)({
|
|
348
|
+
type: "confirm",
|
|
349
|
+
name: "value",
|
|
350
|
+
message: "마이그레이션 코드를 생성하시겠습니까?",
|
|
351
|
+
initial: false,
|
|
352
|
+
});
|
|
353
|
+
if (answer.value === false) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// 실제 코드 생성
|
|
357
|
+
const migrationsDir = `${api_1.Sonamu.apiRootPath}/src/migrations`;
|
|
358
|
+
codes
|
|
359
|
+
.filter((code) => code.formatted)
|
|
360
|
+
.map((code, index) => {
|
|
361
|
+
const dateTag = luxon_1.DateTime.local()
|
|
362
|
+
.plus({ seconds: index })
|
|
363
|
+
.toFormat("yyyyMMddHHmmss");
|
|
364
|
+
const filePath = `${migrationsDir}/${dateTag}_${code.title}.ts`;
|
|
365
|
+
(0, fs_1.writeFileSync)(filePath, code.formatted);
|
|
366
|
+
console.log(chalk_1.default.green(`MIGRTAION CRETATED ${filePath}`));
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
rollback() {
|
|
371
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
372
|
+
console.time(chalk_1.default.red("rollback:"));
|
|
373
|
+
const rollbackAllResult = yield Promise.all(this.targets.apply.map((db) => __awaiter(this, void 0, void 0, function* () {
|
|
374
|
+
yield db.migrate.forceFreeMigrationsLock();
|
|
375
|
+
return db.migrate.rollback(undefined, false);
|
|
376
|
+
})));
|
|
377
|
+
console.dir({ rollbackAllResult }, { depth: null });
|
|
378
|
+
console.timeEnd(chalk_1.default.red("rollback:"));
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
cleanUpDist(force = false) {
|
|
382
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
383
|
+
const files = ["src", "dist"].reduce((r, which) => {
|
|
384
|
+
const migrationPath = path_1.default.join(api_1.Sonamu.apiRootPath, which, "migrations");
|
|
385
|
+
if ((0, fs_1.existsSync)(migrationPath) === false) {
|
|
386
|
+
(0, fs_1.mkdirSync)(migrationPath, {
|
|
387
|
+
recursive: true,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
const files = (0, fs_1.readdirSync)(migrationPath).filter((filename) => filename.startsWith(".") === false);
|
|
391
|
+
r[which] = files;
|
|
392
|
+
return r;
|
|
393
|
+
}, {
|
|
394
|
+
src: [],
|
|
395
|
+
dist: [],
|
|
396
|
+
});
|
|
397
|
+
const diffOnSrc = (0, lodash_1.differenceBy)(files.src, files.dist, (filename) => filename.split(".")[0]);
|
|
398
|
+
if (diffOnSrc.length > 0) {
|
|
399
|
+
throw new Error("컴파일 되지 않은 파일이 있습니다.\n" + diffOnSrc.join("\n"));
|
|
400
|
+
}
|
|
401
|
+
const diffOnDist = (0, lodash_1.differenceBy)(files.dist, files.src, (filename) => filename.split(".")[0]);
|
|
402
|
+
if (diffOnDist.length > 0) {
|
|
403
|
+
console.log(chalk_1.default.red("원본 ts파일을 찾을 수 없는 js파일이 있습니다."));
|
|
404
|
+
console.log(diffOnDist);
|
|
405
|
+
if (!force) {
|
|
406
|
+
const answer = yield (0, prompts_1.default)({
|
|
407
|
+
type: "confirm",
|
|
408
|
+
name: "value",
|
|
409
|
+
message: "삭제를 진행하시겠습니까?",
|
|
410
|
+
initial: true,
|
|
411
|
+
});
|
|
412
|
+
if (answer.value === false) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const filesToRm = diffOnDist.map((filename) => {
|
|
417
|
+
return path_1.default.join(api_1.Sonamu.apiRootPath, "dist", "migrations", filename);
|
|
418
|
+
});
|
|
419
|
+
filesToRm.map((filePath) => {
|
|
420
|
+
(0, fs_1.unlinkSync)(filePath);
|
|
421
|
+
});
|
|
422
|
+
console.log(chalk_1.default.green(`${filesToRm.length}건 삭제되었습니다!`));
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
runShadowTest() {
|
|
427
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
428
|
+
// ShadowDB 생성 후 테스트 진행
|
|
429
|
+
const tdb = (0, knex_1.default)(api_1.Sonamu.dbConfig.test);
|
|
430
|
+
const tdbConn = api_1.Sonamu.dbConfig.test.connection;
|
|
431
|
+
const shadowDatabase = tdbConn.database + "__migration_shadow";
|
|
432
|
+
const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
|
|
433
|
+
// 테스트DB 덤프 후 Database명 치환
|
|
434
|
+
console.log(chalk_1.default.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`));
|
|
435
|
+
(0, child_process_1.execSync)(`mysqldump -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`);
|
|
436
|
+
(0, child_process_1.execSync)(`sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`);
|
|
437
|
+
// 기존 ShadowDB 리셋
|
|
438
|
+
console.log(chalk_1.default.magenta(`${shadowDatabase} 리셋`));
|
|
439
|
+
yield tdb.raw(`DROP DATABASE IF EXISTS ${shadowDatabase};`);
|
|
440
|
+
yield tdb.raw(`CREATE DATABASE ${shadowDatabase};`);
|
|
441
|
+
// ShadowDB 테이블 + 데이터 생성
|
|
442
|
+
console.log(chalk_1.default.magenta(`${shadowDatabase} 데이터베이스 생성`));
|
|
443
|
+
(0, child_process_1.execSync)(`mysql -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`);
|
|
444
|
+
// tdb 연결 종료
|
|
445
|
+
yield tdb.destroy();
|
|
446
|
+
// shadow db 테스트 진행
|
|
447
|
+
const sdb = (0, knex_1.default)(Object.assign(Object.assign({}, api_1.Sonamu.dbConfig.test), { connection: Object.assign(Object.assign({}, tdbConn), { database: shadowDatabase, password: tdbConn.password }) }));
|
|
448
|
+
try {
|
|
449
|
+
const [batchNo, applied] = yield sdb.migrate.latest();
|
|
450
|
+
console.log(chalk_1.default.green("Shadow DB 테스트에 성공했습니다!"), {
|
|
451
|
+
batchNo,
|
|
452
|
+
applied,
|
|
453
|
+
});
|
|
454
|
+
// 생성한 Shadow DB 삭제
|
|
455
|
+
console.log(chalk_1.default.magenta(`${shadowDatabase} 삭제`));
|
|
456
|
+
yield sdb.raw(`DROP DATABASE IF EXISTS ${shadowDatabase};`);
|
|
457
|
+
return [
|
|
458
|
+
{
|
|
459
|
+
connKey: "shadow",
|
|
460
|
+
batchNo,
|
|
461
|
+
applied,
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
console.error(e);
|
|
467
|
+
throw new so_exceptions_1.ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
yield sdb.destroy();
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
resetAll() {
|
|
475
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
476
|
+
const answer = yield (0, prompts_1.default)({
|
|
477
|
+
type: "confirm",
|
|
478
|
+
name: "value",
|
|
479
|
+
message: "모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제하시겠습니까?",
|
|
480
|
+
initial: false,
|
|
481
|
+
});
|
|
482
|
+
if (answer.value === false) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
console.time(chalk_1.default.red("rollback-all:"));
|
|
486
|
+
const rollbackAllResult = yield Promise.all(this.targets.apply.map((db) => __awaiter(this, void 0, void 0, function* () {
|
|
487
|
+
yield db.migrate.forceFreeMigrationsLock();
|
|
488
|
+
return db.migrate.rollback(undefined, true);
|
|
489
|
+
})));
|
|
490
|
+
console.log({ rollbackAllResult });
|
|
491
|
+
console.timeEnd(chalk_1.default.red("rollback-all:"));
|
|
492
|
+
const migrationsDir = `${api_1.Sonamu.apiRootPath}/src/migrations`;
|
|
493
|
+
console.time(chalk_1.default.red("delete migration files"));
|
|
494
|
+
(0, child_process_1.execSync)(`rm -f ${migrationsDir}/*`);
|
|
495
|
+
(0, child_process_1.execSync)(`rm -f ${migrationsDir.replace("/src/", "/dist/")}/*`);
|
|
496
|
+
console.timeEnd(chalk_1.default.red("delete migration files"));
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
compareMigrations(compareDB) {
|
|
500
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
501
|
+
// MD 순회하여 싱크
|
|
502
|
+
const entityIds = entity_manager_1.EntityManager.getAllIds();
|
|
503
|
+
// 조인테이블 포함하여 MD에서 MigrationSet 추출
|
|
504
|
+
const entitySetsWithJoinTable = entityIds
|
|
505
|
+
.filter((entityId) => {
|
|
506
|
+
const entity = entity_manager_1.EntityManager.get(entityId);
|
|
507
|
+
return entity.props.length > 0;
|
|
508
|
+
})
|
|
509
|
+
.map((entityId) => {
|
|
510
|
+
const entity = entity_manager_1.EntityManager.get(entityId);
|
|
511
|
+
return this.getMigrationSetFromMD(entity);
|
|
512
|
+
});
|
|
513
|
+
// 조인테이블만 추출
|
|
514
|
+
const joinTables = (0, lodash_1.uniqBy)(entitySetsWithJoinTable.map((entitySet) => entitySet.joinTables).flat(), (joinTable) => {
|
|
515
|
+
return joinTable.table;
|
|
516
|
+
});
|
|
517
|
+
// 조인테이블 포함하여 MigrationSet 배열
|
|
518
|
+
const entitySets = [
|
|
519
|
+
...entitySetsWithJoinTable,
|
|
520
|
+
...joinTables,
|
|
521
|
+
];
|
|
522
|
+
let codes = (yield Promise.all(entitySets.map((entitySet) => __awaiter(this, void 0, void 0, function* () {
|
|
523
|
+
const dbSet = yield this.getMigrationSetFromDB(compareDB, entitySet.table);
|
|
524
|
+
if (dbSet === null) {
|
|
525
|
+
// 기존 테이블 없음, 새로 테이블 생성
|
|
526
|
+
return [
|
|
527
|
+
this.generateCreateCode_ColumnAndIndexes(entitySet.table, entitySet.columns, entitySet.indexes),
|
|
528
|
+
...this.generateCreateCode_Foreign(entitySet.table, entitySet.foreigns),
|
|
529
|
+
];
|
|
530
|
+
}
|
|
531
|
+
// 기존 테이블 존재하는 케이스
|
|
532
|
+
const alterCodes = ["columnsAndIndexes", "foreigns"].map((key) => {
|
|
533
|
+
// 배열 원소의 순서가 달라서 불일치가 발생하는걸 방지하기 위해 각 항목별로 정렬 처리 후 비교
|
|
534
|
+
if (key === "columnsAndIndexes") {
|
|
535
|
+
const entityColumns = (0, lodash_1.sortBy)(entitySet.columns, (a) => a.name);
|
|
536
|
+
const dbColumns = (0, lodash_1.sortBy)(dbSet.columns, (a) => a.name);
|
|
537
|
+
/* 디버깅용 코드, 특정 컬럼에서 불일치 발생할 때 확인
|
|
538
|
+
const entityCreatedAt = entitySet.columns.find(
|
|
539
|
+
(col) => col.name === "created_at"
|
|
540
|
+
);
|
|
541
|
+
const dbCreatedAt = dbSet.columns.find(
|
|
542
|
+
(col) => col.name === "created_at"
|
|
543
|
+
);
|
|
544
|
+
console.debug({ entityCreatedAt, dbCreatedAt });
|
|
545
|
+
*/
|
|
546
|
+
const entityIndexes = (0, lodash_1.sortBy)(entitySet.indexes, (a) => [
|
|
547
|
+
a.type,
|
|
548
|
+
...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1)),
|
|
549
|
+
].join("-"));
|
|
550
|
+
const dbIndexes = (0, lodash_1.sortBy)(dbSet.indexes, (a) => [
|
|
551
|
+
a.type,
|
|
552
|
+
...a.columns.sort((c1, c2) => (c1 > c2 ? 1 : -1)),
|
|
553
|
+
].join("-"));
|
|
554
|
+
const isEqualColumns = (0, fast_deep_equal_1.default)(entityColumns, dbColumns);
|
|
555
|
+
const isEqualIndexes = (0, fast_deep_equal_1.default)(entityIndexes, dbIndexes);
|
|
556
|
+
if (isEqualColumns && isEqualIndexes) {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
// this.showMigrationSet("MD", entitySet);
|
|
561
|
+
// this.showMigrationSet("DB", dbSet);
|
|
562
|
+
return this.generateAlterCode_ColumnAndIndexes(entitySet.table, entityColumns, entityIndexes, dbColumns, dbIndexes);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
const replaceNoActionOnMySQL = (f) => {
|
|
567
|
+
// MySQL에서 RESTRICT와 NO ACTION은 동일함
|
|
568
|
+
const { onDelete, onUpdate } = f;
|
|
569
|
+
return Object.assign(Object.assign({}, f), { onUpdate: onUpdate === "RESTRICT" ? "NO ACTION" : onUpdate, onDelete: onDelete === "RESTRICT" ? "NO ACTION" : onDelete });
|
|
570
|
+
};
|
|
571
|
+
const entityForeigns = (0, lodash_1.sortBy)(entitySet.foreigns, (a) => [a.to, ...a.columns].join("-")).map((f) => replaceNoActionOnMySQL(f));
|
|
572
|
+
const dbForeigns = (0, lodash_1.sortBy)(dbSet.foreigns, (a) => [a.to, ...a.columns].join("-")).map((f) => replaceNoActionOnMySQL(f));
|
|
573
|
+
if ((0, fast_deep_equal_1.default)(entityForeigns, dbForeigns) === false) {
|
|
574
|
+
console.dir({ entityForeigns, dbForeigns }, { depth: null });
|
|
575
|
+
return this.generateAlterCode_Foreigns(entitySet.table, entityForeigns, dbForeigns);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
});
|
|
580
|
+
if (alterCodes.every((alterCode) => alterCode === null)) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
return alterCodes.filter((alterCode) => alterCode !== null).flat();
|
|
585
|
+
}
|
|
586
|
+
}))))
|
|
587
|
+
.flat()
|
|
588
|
+
.filter((code) => code !== null);
|
|
589
|
+
/*
|
|
590
|
+
normal 타입이 앞으로, foreign 이 뒤로
|
|
591
|
+
*/
|
|
592
|
+
codes.sort((codeA, codeB) => {
|
|
593
|
+
if (codeA.type === "foreign" && codeB.type == "normal") {
|
|
594
|
+
return 1;
|
|
595
|
+
}
|
|
596
|
+
else if (codeA.type === "normal" && codeB.type === "foreign") {
|
|
597
|
+
return -1;
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
return 0;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
return codes;
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/*
|
|
607
|
+
기존 테이블 정보 읽어서 MigrationSet 형식으로 리턴
|
|
608
|
+
*/
|
|
609
|
+
getMigrationSetFromDB(compareDB, table) {
|
|
610
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
611
|
+
let dbColumns, dbIndexes, dbForeigns;
|
|
612
|
+
try {
|
|
613
|
+
[dbColumns, dbIndexes, dbForeigns] = yield this.readTable(compareDB, table);
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
if ((0, types_1.isKnexError)(e) && e.code === "ER_NO_SUCH_TABLE") {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
console.error(e);
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
const columns = dbColumns.map((dbColumn) => {
|
|
623
|
+
var _a;
|
|
624
|
+
const dbColType = this.resolveDBColType(dbColumn.Type, dbColumn.Field);
|
|
625
|
+
return Object.assign(Object.assign({ name: dbColumn.Field, nullable: dbColumn.Null !== "NO" }, dbColType), (0, lodash_able_1.propIf)(dbColumn.Default !== null, {
|
|
626
|
+
defaultTo: dbColType.type === "float"
|
|
627
|
+
? parseFloat((_a = dbColumn.Default) !== null && _a !== void 0 ? _a : "0").toString()
|
|
628
|
+
: dbColumn.Default,
|
|
629
|
+
}));
|
|
630
|
+
});
|
|
631
|
+
const dbIndexesGroup = (0, lodash_1.groupBy)(dbIndexes.filter((dbIndex) => dbIndex.Key_name !== "PRIMARY" &&
|
|
632
|
+
!dbForeigns.find((dbForeign) => dbForeign.keyName === dbIndex.Key_name)), (dbIndex) => dbIndex.Key_name);
|
|
633
|
+
// indexes 처리
|
|
634
|
+
const indexes = Object.keys(dbIndexesGroup).map((keyName) => {
|
|
635
|
+
const currentIndexes = dbIndexesGroup[keyName];
|
|
636
|
+
return {
|
|
637
|
+
type: currentIndexes[0].Non_unique === 1 ? "index" : "unique",
|
|
638
|
+
columns: currentIndexes.map((currentIndex) => currentIndex.Column_name),
|
|
639
|
+
};
|
|
640
|
+
});
|
|
641
|
+
// console.log(table);
|
|
642
|
+
// console.table(dbIndexes);
|
|
643
|
+
// console.table(dbForeigns);
|
|
644
|
+
// foreigns 처리
|
|
645
|
+
const foreigns = dbForeigns.map((dbForeign) => {
|
|
646
|
+
return {
|
|
647
|
+
columns: [dbForeign.from],
|
|
648
|
+
to: `${dbForeign.referencesTable}.${dbForeign.referencesField}`,
|
|
649
|
+
onUpdate: dbForeign.onUpdate,
|
|
650
|
+
onDelete: dbForeign.onDelete,
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
return {
|
|
654
|
+
table,
|
|
655
|
+
columns,
|
|
656
|
+
indexes,
|
|
657
|
+
foreigns,
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
resolveDBColType(colType, colField) {
|
|
662
|
+
var _a, _b;
|
|
663
|
+
let [rawType, unsigned] = colType.split(" ");
|
|
664
|
+
const matched = rawType.match(/\(([0-9]+)\)/);
|
|
665
|
+
let length;
|
|
666
|
+
if (matched !== null && matched[1]) {
|
|
667
|
+
rawType = rawType.replace(/\(([0-9]+)\)/, "");
|
|
668
|
+
length = parseInt(matched[1]);
|
|
669
|
+
}
|
|
670
|
+
if (rawType === "char" && colField === "uuid") {
|
|
671
|
+
return Object.assign({ type: "uuid" }, (0, lodash_able_1.propIf)(length !== undefined, {}));
|
|
672
|
+
}
|
|
673
|
+
switch (rawType) {
|
|
674
|
+
case "int":
|
|
675
|
+
return {
|
|
676
|
+
type: "integer",
|
|
677
|
+
unsigned: unsigned === "unsigned",
|
|
678
|
+
};
|
|
679
|
+
case "varchar":
|
|
680
|
+
// case "char":
|
|
681
|
+
return Object.assign({ type: "string" }, (0, lodash_able_1.propIf)(length !== undefined, {
|
|
682
|
+
length,
|
|
683
|
+
}));
|
|
684
|
+
case "text":
|
|
685
|
+
case "mediumtext":
|
|
686
|
+
case "longtext":
|
|
687
|
+
case "timestamp":
|
|
688
|
+
case "json":
|
|
689
|
+
case "date":
|
|
690
|
+
case "time":
|
|
691
|
+
return {
|
|
692
|
+
type: rawType,
|
|
693
|
+
};
|
|
694
|
+
case "datetime":
|
|
695
|
+
return {
|
|
696
|
+
type: "dateTime",
|
|
697
|
+
};
|
|
698
|
+
case "tinyint":
|
|
699
|
+
return {
|
|
700
|
+
type: "boolean",
|
|
701
|
+
};
|
|
702
|
+
default:
|
|
703
|
+
// decimal 처리
|
|
704
|
+
if (rawType.startsWith("decimal")) {
|
|
705
|
+
const [, precision, scale] = (_a = rawType.match(/decimal\(([0-9]+),([0-9]+)\)/)) !== null && _a !== void 0 ? _a : [];
|
|
706
|
+
return Object.assign({ type: "decimal", precision: parseInt(precision), scale: parseInt(scale) }, (0, lodash_able_1.propIf)(unsigned === "unsigned", {
|
|
707
|
+
unsigned: true,
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
else if (rawType.startsWith("float")) {
|
|
711
|
+
const [, precision, scale] = (_b = rawType.match(/float\(([0-9]+),([0-9]+)\)/)) !== null && _b !== void 0 ? _b : [];
|
|
712
|
+
return Object.assign({ type: "float", precision: parseInt(precision), scale: parseInt(scale) }, (0, lodash_able_1.propIf)(unsigned === "unsigned", {
|
|
713
|
+
unsigned: true,
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
throw new Error(`resolve 불가능한 DB컬럼 타입 ${colType} ${rawType}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/*
|
|
720
|
+
기존 테이블 읽어서 cols, indexes 반환
|
|
721
|
+
*/
|
|
722
|
+
readTable(compareDB, tableName) {
|
|
723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
724
|
+
// 테이블 정보
|
|
725
|
+
try {
|
|
726
|
+
const [cols] = yield compareDB.raw(`SHOW FIELDS FROM ${tableName}`);
|
|
727
|
+
const [indexes] = yield compareDB.raw(`SHOW INDEX FROM ${tableName}`);
|
|
728
|
+
const [[row]] = yield compareDB.raw(`SHOW CREATE TABLE ${tableName}`);
|
|
729
|
+
const ddl = row["Create Table"];
|
|
730
|
+
const matched = ddl.match(/CONSTRAINT .+/g);
|
|
731
|
+
const foreignKeys = (matched !== null && matched !== void 0 ? matched : []).map((line) => {
|
|
732
|
+
// 해당 라인을 정규식으로 파싱
|
|
733
|
+
const matched = line.match(/CONSTRAINT `(.+)` FOREIGN KEY \(`(.+)`\) REFERENCES `(.+)` \(`(.+)`\)( ON DELETE [A-Z ]+ ON UPDATE [A-Z ]+)*/);
|
|
734
|
+
if (!matched) {
|
|
735
|
+
throw new Error(`인식할 수 없는 FOREIGN KEY CONSTRAINT ${line}`);
|
|
736
|
+
}
|
|
737
|
+
const [, keyName, from, referencesTable, referencesField, _onClause] = matched;
|
|
738
|
+
const onClause = _onClause !== null && _onClause !== void 0 ? _onClause : "";
|
|
739
|
+
const { onDelete, onUpdate } = (() => {
|
|
740
|
+
var _a, _b, _c, _d;
|
|
741
|
+
// ON Clause가 둘다 있는 경우
|
|
742
|
+
if (onClause.includes("ON DELETE") &&
|
|
743
|
+
onClause.includes("ON UPDATE")) {
|
|
744
|
+
const [, onDelete, onUpdate] = onClause.match(/ON DELETE ([A-Z ]+) ON UPDATE ([A-Z ]+)/);
|
|
745
|
+
return {
|
|
746
|
+
onDelete,
|
|
747
|
+
onUpdate,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
// 각각 있는 경우
|
|
751
|
+
const onDelete = (_b = (_a = onClause.match(/ON DELETE ([A-Z ]+)( ON)*/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "NO ACTION";
|
|
752
|
+
const onUpdate = (_d = (_c = onClause.match(/ON UPDATE ([A-Z ]+)/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : "NO ACTION";
|
|
753
|
+
return {
|
|
754
|
+
onDelete,
|
|
755
|
+
onUpdate,
|
|
756
|
+
};
|
|
757
|
+
})();
|
|
758
|
+
return {
|
|
759
|
+
keyName,
|
|
760
|
+
from,
|
|
761
|
+
referencesTable,
|
|
762
|
+
referencesField,
|
|
763
|
+
onDelete,
|
|
764
|
+
onUpdate,
|
|
765
|
+
};
|
|
766
|
+
});
|
|
767
|
+
return [cols, indexes, foreignKeys];
|
|
768
|
+
}
|
|
769
|
+
catch (e) {
|
|
770
|
+
throw e;
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/*
|
|
775
|
+
MD 내용 읽어서 MigrationSetAndJoinTable 추출
|
|
776
|
+
*/
|
|
777
|
+
getMigrationSetFromMD(entity) {
|
|
778
|
+
const migrationSet = entity.props.reduce((r, prop) => {
|
|
779
|
+
var _a, _b, _c;
|
|
780
|
+
// virtual 필드 제외
|
|
781
|
+
if ((0, types_1.isVirtualProp)(prop)) {
|
|
782
|
+
return r;
|
|
783
|
+
}
|
|
784
|
+
// HasMany 케이스는 아무 처리도 하지 않음
|
|
785
|
+
if ((0, types_1.isHasManyRelationProp)(prop)) {
|
|
786
|
+
return r;
|
|
787
|
+
}
|
|
788
|
+
// 일반 컬럼
|
|
789
|
+
if (!(0, types_1.isRelationProp)(prop)) {
|
|
790
|
+
// type resolve
|
|
791
|
+
let type;
|
|
792
|
+
if ((0, types_1.isTextProp)(prop)) {
|
|
793
|
+
type = prop.textType;
|
|
794
|
+
}
|
|
795
|
+
else if ((0, types_1.isEnumProp)(prop)) {
|
|
796
|
+
type = "string";
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
type = prop.type;
|
|
800
|
+
}
|
|
801
|
+
const column = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ name: prop.name, type }, ((0, types_1.isIntegerProp)(prop)
|
|
802
|
+
? { unsigned: prop.unsigned === true }
|
|
803
|
+
: {})), ((0, types_1.isStringProp)(prop) || (0, types_1.isEnumProp)(prop)
|
|
804
|
+
? { length: prop.length }
|
|
805
|
+
: {})), { nullable: prop.nullable === true }), (0, lodash_able_1.propIf)(prop.dbDefault !== undefined, {
|
|
806
|
+
defaultTo: "" + prop.dbDefault,
|
|
807
|
+
})), ((0, types_1.isDecimalProp)(prop) || (0, types_1.isFloatProp)(prop)
|
|
808
|
+
? {
|
|
809
|
+
precision: (_a = prop.precision) !== null && _a !== void 0 ? _a : 8,
|
|
810
|
+
scale: (_b = prop.scale) !== null && _b !== void 0 ? _b : 2,
|
|
811
|
+
}
|
|
812
|
+
: {}));
|
|
813
|
+
r.columns.push(column);
|
|
814
|
+
}
|
|
815
|
+
if ((0, types_1.isManyToManyRelationProp)(prop)) {
|
|
816
|
+
// ManyToMany 케이스
|
|
817
|
+
const relMd = entity_manager_1.EntityManager.get(prop.with);
|
|
818
|
+
const [table1, table2] = prop.joinTable.split("__");
|
|
819
|
+
const join = {
|
|
820
|
+
from: `${entity.table}.id`,
|
|
821
|
+
through: {
|
|
822
|
+
from: `${prop.joinTable}.${(0, inflection_1.singularize)(table1)}_id`,
|
|
823
|
+
to: `${prop.joinTable}.${(0, inflection_1.singularize)(table2)}_id`,
|
|
824
|
+
onUpdate: prop.onUpdate,
|
|
825
|
+
onDelete: prop.onDelete,
|
|
826
|
+
},
|
|
827
|
+
to: `${relMd.table}.id`,
|
|
828
|
+
};
|
|
829
|
+
const through = join.through;
|
|
830
|
+
const fields = [through.from, through.to];
|
|
831
|
+
r.joinTables.push({
|
|
832
|
+
table: through.from.split(".")[0],
|
|
833
|
+
indexes: [
|
|
834
|
+
{
|
|
835
|
+
type: "unique",
|
|
836
|
+
columns: ["uuid"],
|
|
837
|
+
},
|
|
838
|
+
// 조인 테이블에 걸린 인덱스 찾아와서 연결
|
|
839
|
+
...entity.indexes
|
|
840
|
+
.filter((index) => index.columns.find((col) => col.includes(prop.joinTable + ".")))
|
|
841
|
+
.map((index) => (Object.assign(Object.assign({}, index), { columns: index.columns.map((col) => col.replace(prop.joinTable + ".", "")) }))),
|
|
842
|
+
],
|
|
843
|
+
columns: [
|
|
844
|
+
{
|
|
845
|
+
name: "id",
|
|
846
|
+
type: "integer",
|
|
847
|
+
nullable: false,
|
|
848
|
+
unsigned: true,
|
|
849
|
+
},
|
|
850
|
+
...fields.map((field) => {
|
|
851
|
+
return {
|
|
852
|
+
name: field.split(".")[1],
|
|
853
|
+
type: "integer",
|
|
854
|
+
nullable: false,
|
|
855
|
+
unsigned: true,
|
|
856
|
+
};
|
|
857
|
+
}),
|
|
858
|
+
{
|
|
859
|
+
name: "uuid",
|
|
860
|
+
nullable: true,
|
|
861
|
+
type: "uuid",
|
|
862
|
+
},
|
|
863
|
+
],
|
|
864
|
+
foreigns: fields.map((field) => {
|
|
865
|
+
return {
|
|
866
|
+
columns: [field.split(".")[1]],
|
|
867
|
+
to: through.to.includes(field) ? join.to : join.from,
|
|
868
|
+
onUpdate: through.onUpdate,
|
|
869
|
+
onDelete: through.onDelete,
|
|
870
|
+
};
|
|
871
|
+
}),
|
|
872
|
+
});
|
|
873
|
+
return r;
|
|
874
|
+
}
|
|
875
|
+
else if ((0, types_1.isBelongsToOneRelationProp)(prop) ||
|
|
876
|
+
((0, types_1.isOneToOneRelationProp)(prop) && prop.hasJoinColumn)) {
|
|
877
|
+
// -OneRelation 케이스
|
|
878
|
+
const idColumnName = prop.name + "_id";
|
|
879
|
+
r.columns.push({
|
|
880
|
+
name: idColumnName,
|
|
881
|
+
type: "integer",
|
|
882
|
+
unsigned: true,
|
|
883
|
+
nullable: (_c = prop.nullable) !== null && _c !== void 0 ? _c : false,
|
|
884
|
+
});
|
|
885
|
+
r.foreigns.push({
|
|
886
|
+
columns: [idColumnName],
|
|
887
|
+
to: `${(0, inflection_1.underscore)((0, inflection_1.pluralize)(prop.with)).toLowerCase()}.id`,
|
|
888
|
+
onUpdate: prop.onUpdate,
|
|
889
|
+
onDelete: prop.onDelete,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
return r;
|
|
893
|
+
}, {
|
|
894
|
+
table: entity.table,
|
|
895
|
+
columns: [],
|
|
896
|
+
indexes: [],
|
|
897
|
+
foreigns: [],
|
|
898
|
+
joinTables: [],
|
|
899
|
+
});
|
|
900
|
+
// indexes
|
|
901
|
+
migrationSet.indexes = entity.indexes.filter((index) => index.columns.find((col) => col.includes(".") === false));
|
|
902
|
+
// uuid
|
|
903
|
+
migrationSet.columns = migrationSet.columns.concat({
|
|
904
|
+
name: "uuid",
|
|
905
|
+
nullable: true,
|
|
906
|
+
type: "uuid",
|
|
907
|
+
});
|
|
908
|
+
migrationSet.indexes = migrationSet.indexes.concat({
|
|
909
|
+
type: "unique",
|
|
910
|
+
columns: ["uuid"],
|
|
911
|
+
});
|
|
912
|
+
return migrationSet;
|
|
913
|
+
}
|
|
914
|
+
/*
|
|
915
|
+
MigrationColumn[] 읽어서 컬럼 정의하는 구문 생성
|
|
916
|
+
*/
|
|
917
|
+
genColumnDefinitions(columns) {
|
|
918
|
+
return columns.map((column) => {
|
|
919
|
+
const chains = [];
|
|
920
|
+
if (column.name === "id") {
|
|
921
|
+
return `table.increments().primary();`;
|
|
922
|
+
}
|
|
923
|
+
if (column.type === "float" || column.type === "decimal") {
|
|
924
|
+
chains.push(`${column.type}('${column.name}', ${column.precision}, ${column.scale})`);
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
// type, length
|
|
928
|
+
let columnType = column.type;
|
|
929
|
+
let extraType;
|
|
930
|
+
if (columnType.includes("text") && columnType !== "text") {
|
|
931
|
+
extraType = columnType;
|
|
932
|
+
columnType = "text";
|
|
933
|
+
}
|
|
934
|
+
chains.push(`${column.type}('${column.name}'${column.length ? `, ${column.length}` : ""}${extraType ? `, '${extraType}'` : ""})`);
|
|
935
|
+
}
|
|
936
|
+
if (column.unsigned) {
|
|
937
|
+
chains.push("unsigned()");
|
|
938
|
+
}
|
|
939
|
+
// nullable
|
|
940
|
+
chains.push(column.nullable ? "nullable()" : "notNullable()");
|
|
941
|
+
// defaultTo
|
|
942
|
+
if (column.defaultTo !== undefined) {
|
|
943
|
+
chains.push(`defaultTo(knex.raw('${column.defaultTo}'))`);
|
|
944
|
+
}
|
|
945
|
+
return `table.${chains.join(".")};`;
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
/*
|
|
949
|
+
MigrationIndex[] 읽어서 인덱스/유니크 정의하는 구문 생성
|
|
950
|
+
*/
|
|
951
|
+
genIndexDefinitions(indexes) {
|
|
952
|
+
if (indexes.length === 0) {
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
const lines = (0, lodash_1.uniq)(indexes.reduce((r, index) => {
|
|
956
|
+
r.push(`table.${index.type}([${index.columns
|
|
957
|
+
.map((col) => `'${col}'`)
|
|
958
|
+
.join(",")}])`);
|
|
959
|
+
return r;
|
|
960
|
+
}, []));
|
|
961
|
+
return lines;
|
|
962
|
+
}
|
|
963
|
+
/*
|
|
964
|
+
MigrationForeign[] 읽어서 외부키 constraint 정의하는 구문 생성
|
|
965
|
+
*/
|
|
966
|
+
genForeignDefinitions(table, foreigns) {
|
|
967
|
+
return foreigns.reduce((r, foreign) => {
|
|
968
|
+
const columnsStringQuote = foreign.columns
|
|
969
|
+
.map((col) => `'${col.replace(`${table}.`, "")}'`)
|
|
970
|
+
.join(",");
|
|
971
|
+
r.up.push(`table.foreign('${foreign.columns.join(",")}')
|
|
972
|
+
.references('${foreign.to}')
|
|
973
|
+
.onUpdate('${foreign.onUpdate}')
|
|
974
|
+
.onDelete('${foreign.onDelete}')`);
|
|
975
|
+
r.down.push(`table.dropForeign([${columnsStringQuote}])`);
|
|
976
|
+
return r;
|
|
977
|
+
}, {
|
|
978
|
+
up: [],
|
|
979
|
+
down: [],
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/*
|
|
983
|
+
테이블 생성하는 케이스 - 컬럼/인덱스 생성
|
|
984
|
+
*/
|
|
985
|
+
generateCreateCode_ColumnAndIndexes(table, columns, indexes) {
|
|
986
|
+
// 컬럼, 인덱스 처리
|
|
987
|
+
const lines = [
|
|
988
|
+
'import { Knex } from "knex";',
|
|
989
|
+
"",
|
|
990
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
991
|
+
`return knex.schema.createTable("${table}", (table) => {`,
|
|
992
|
+
"// columns",
|
|
993
|
+
...this.genColumnDefinitions(columns),
|
|
994
|
+
"",
|
|
995
|
+
"// indexes",
|
|
996
|
+
...this.genIndexDefinitions(indexes),
|
|
997
|
+
"});",
|
|
998
|
+
"}",
|
|
999
|
+
"",
|
|
1000
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
1001
|
+
` return knex.schema.dropTable("${table}");`,
|
|
1002
|
+
"}",
|
|
1003
|
+
];
|
|
1004
|
+
return {
|
|
1005
|
+
table,
|
|
1006
|
+
type: "normal",
|
|
1007
|
+
title: `create__${table}`,
|
|
1008
|
+
formatted: prettier_1.default.format(lines.join("\n"), {
|
|
1009
|
+
parser: "typescript",
|
|
1010
|
+
}),
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
/*
|
|
1014
|
+
테이블 생성하는 케이스 - FK 생성
|
|
1015
|
+
*/
|
|
1016
|
+
generateCreateCode_Foreign(table, foreigns) {
|
|
1017
|
+
if (foreigns.length === 0) {
|
|
1018
|
+
return [];
|
|
1019
|
+
}
|
|
1020
|
+
const { up, down } = this.genForeignDefinitions(table, foreigns);
|
|
1021
|
+
if (up.length === 0 && down.length === 0) {
|
|
1022
|
+
console.log("fk 가 뭔가 다릅니다");
|
|
1023
|
+
return [];
|
|
1024
|
+
}
|
|
1025
|
+
const lines = [
|
|
1026
|
+
'import { Knex } from "knex";',
|
|
1027
|
+
"",
|
|
1028
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
1029
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1030
|
+
"// create fk",
|
|
1031
|
+
...up,
|
|
1032
|
+
"});",
|
|
1033
|
+
"}",
|
|
1034
|
+
"",
|
|
1035
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
1036
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1037
|
+
"// drop fk",
|
|
1038
|
+
...down,
|
|
1039
|
+
"});",
|
|
1040
|
+
"}",
|
|
1041
|
+
];
|
|
1042
|
+
const foreignKeysString = foreigns
|
|
1043
|
+
.map((foreign) => foreign.columns.join("_"))
|
|
1044
|
+
.join("_");
|
|
1045
|
+
return [
|
|
1046
|
+
{
|
|
1047
|
+
table,
|
|
1048
|
+
type: "foreign",
|
|
1049
|
+
title: `foreign__${table}__${foreignKeysString}`,
|
|
1050
|
+
formatted: prettier_1.default.format(lines.join("\n"), {
|
|
1051
|
+
parser: "typescript",
|
|
1052
|
+
}),
|
|
1053
|
+
},
|
|
1054
|
+
];
|
|
1055
|
+
}
|
|
1056
|
+
/*
|
|
1057
|
+
마이그레이션 컬럼 배열 비교용 코드
|
|
1058
|
+
*/
|
|
1059
|
+
showMigrationSet(which, migrationSet) {
|
|
1060
|
+
const { columns, indexes, foreigns } = migrationSet;
|
|
1061
|
+
const styledChalk = which === "MD" ? chalk_1.default.bgGreen.black : chalk_1.default.bgBlue.black;
|
|
1062
|
+
console.log(styledChalk(`${which} ${migrationSet.table} Columns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`));
|
|
1063
|
+
console.table(columns.map((column) => {
|
|
1064
|
+
return Object.assign({}, (0, lodash_1.pick)(column, [
|
|
1065
|
+
"name",
|
|
1066
|
+
"type",
|
|
1067
|
+
"nullable",
|
|
1068
|
+
"unsigned",
|
|
1069
|
+
"length",
|
|
1070
|
+
"defaultTo",
|
|
1071
|
+
"precision",
|
|
1072
|
+
"scale",
|
|
1073
|
+
]));
|
|
1074
|
+
}), [
|
|
1075
|
+
"name",
|
|
1076
|
+
"type",
|
|
1077
|
+
"nullable",
|
|
1078
|
+
"unsigned",
|
|
1079
|
+
"length",
|
|
1080
|
+
"defaultTo",
|
|
1081
|
+
"precision",
|
|
1082
|
+
"scale",
|
|
1083
|
+
]);
|
|
1084
|
+
if (indexes.length > 0) {
|
|
1085
|
+
console.log(styledChalk(`${which} ${migrationSet.table} Indexes\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`));
|
|
1086
|
+
console.table(indexes.map((index) => {
|
|
1087
|
+
return Object.assign({}, (0, lodash_1.pick)(index, ["type", "columns", "name"]));
|
|
1088
|
+
}));
|
|
1089
|
+
}
|
|
1090
|
+
if (foreigns.length > 0) {
|
|
1091
|
+
console.log(chalk_1.default.bgMagenta.black(`${which} ${migrationSet.table} Foreigns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`));
|
|
1092
|
+
console.table(foreigns.map((foreign) => {
|
|
1093
|
+
return Object.assign({}, (0, lodash_1.pick)(foreign, ["columns", "to", "onUpdate", "onDelete"]));
|
|
1094
|
+
}));
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
generateAlterCode_ColumnAndIndexes(table, entityColumns, entityIndexes, dbColumns, dbIndexes) {
|
|
1098
|
+
/*
|
|
1099
|
+
세부 비교 후 다른점 찾아서 코드 생성
|
|
1100
|
+
|
|
1101
|
+
1. 컬럼갯수 다름: MD에 있으나, DB에 없다면 추가
|
|
1102
|
+
2. 컬럼갯수 다름: MD에 없으나, DB에 있다면 삭제
|
|
1103
|
+
3. 그외 컬럼(컬럼 갯수가 동일하거나, 다른 경우 동일한 컬럼끼리) => alter
|
|
1104
|
+
4. 다른거 다 동일하고 index만 변경되는 경우
|
|
1105
|
+
|
|
1106
|
+
** 컬럼명을 변경하는 경우는 따로 핸들링하지 않음
|
|
1107
|
+
=> drop/add 형태의 마이그레이션 코드가 생성되는데, 수동으로 rename 코드로 수정하여 처리
|
|
1108
|
+
*/
|
|
1109
|
+
// 각 컬럼 이름 기준으로 add, drop, alter 여부 확인
|
|
1110
|
+
const alterColumnsTo = this.getAlterColumnsTo(entityColumns, dbColumns);
|
|
1111
|
+
// 추출된 컬럼들을 기준으로 각각 라인 생성
|
|
1112
|
+
const alterColumnLinesTo = this.getAlterColumnLinesTo(alterColumnsTo, entityColumns);
|
|
1113
|
+
// 인덱스의 add, drop 여부 확인
|
|
1114
|
+
const alterIndexesTo = this.getAlterIndexesTo(entityIndexes, dbIndexes);
|
|
1115
|
+
// 추출된 인덱스들을 기준으로 각각 라인 생성
|
|
1116
|
+
const alterIndexLinesTo = this.getAlterIndexLinesTo(alterIndexesTo, alterColumnsTo);
|
|
1117
|
+
const lines = [
|
|
1118
|
+
'import { Knex } from "knex";',
|
|
1119
|
+
"",
|
|
1120
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
1121
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1122
|
+
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.up : []),
|
|
1123
|
+
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.up : []),
|
|
1124
|
+
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.up : []),
|
|
1125
|
+
...(alterIndexesTo.add.length > 0 ? alterIndexLinesTo.add.up : []),
|
|
1126
|
+
...(alterIndexesTo.drop.length > 0 ? alterIndexLinesTo.drop.up : []),
|
|
1127
|
+
"})",
|
|
1128
|
+
"}",
|
|
1129
|
+
"",
|
|
1130
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
1131
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1132
|
+
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.down : []),
|
|
1133
|
+
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.down : []),
|
|
1134
|
+
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.down : []),
|
|
1135
|
+
...(alterIndexLinesTo.add.down.length > 1
|
|
1136
|
+
? alterIndexLinesTo.add.down
|
|
1137
|
+
: []),
|
|
1138
|
+
...(alterIndexLinesTo.drop.down.length > 1
|
|
1139
|
+
? alterIndexLinesTo.drop.down
|
|
1140
|
+
: []),
|
|
1141
|
+
"})",
|
|
1142
|
+
"}",
|
|
1143
|
+
];
|
|
1144
|
+
const formatted = prettier_1.default.format(lines.join("\n"), {
|
|
1145
|
+
parser: "typescript",
|
|
1146
|
+
});
|
|
1147
|
+
const title = [
|
|
1148
|
+
"alter",
|
|
1149
|
+
table,
|
|
1150
|
+
...["add", "drop", "alter"]
|
|
1151
|
+
.map((action) => {
|
|
1152
|
+
const len = alterColumnsTo[action].length;
|
|
1153
|
+
if (len > 0) {
|
|
1154
|
+
return action + len;
|
|
1155
|
+
}
|
|
1156
|
+
return null;
|
|
1157
|
+
})
|
|
1158
|
+
.filter((part) => part !== null),
|
|
1159
|
+
].join("_");
|
|
1160
|
+
return [
|
|
1161
|
+
{
|
|
1162
|
+
table,
|
|
1163
|
+
title,
|
|
1164
|
+
formatted,
|
|
1165
|
+
type: "normal",
|
|
1166
|
+
},
|
|
1167
|
+
];
|
|
1168
|
+
}
|
|
1169
|
+
getAlterColumnsTo(entityColumns, dbColumns) {
|
|
1170
|
+
const columnsTo = {
|
|
1171
|
+
add: [],
|
|
1172
|
+
drop: [],
|
|
1173
|
+
alter: [],
|
|
1174
|
+
};
|
|
1175
|
+
// 컬럼명 기준 비교
|
|
1176
|
+
const extraColumns = {
|
|
1177
|
+
db: (0, lodash_1.differenceBy)(dbColumns, entityColumns, (col) => col.name),
|
|
1178
|
+
entity: (0, lodash_1.differenceBy)(entityColumns, dbColumns, (col) => col.name),
|
|
1179
|
+
};
|
|
1180
|
+
if (extraColumns.entity.length > 0) {
|
|
1181
|
+
columnsTo.add = columnsTo.add.concat(extraColumns.entity);
|
|
1182
|
+
}
|
|
1183
|
+
if (extraColumns.db.length > 0) {
|
|
1184
|
+
columnsTo.drop = columnsTo.drop.concat(extraColumns.db);
|
|
1185
|
+
}
|
|
1186
|
+
// 동일 컬럼명의 세부 필드 비교
|
|
1187
|
+
const sameDbColumns = (0, lodash_1.intersectionBy)(dbColumns, entityColumns, (col) => col.name);
|
|
1188
|
+
const sameMdColumns = (0, lodash_1.intersectionBy)(entityColumns, dbColumns, (col) => col.name);
|
|
1189
|
+
columnsTo.alter = (0, lodash_1.differenceWith)(sameDbColumns, sameMdColumns, (a, b) => (0, fast_deep_equal_1.default)(a, b));
|
|
1190
|
+
return columnsTo;
|
|
1191
|
+
}
|
|
1192
|
+
getAlterColumnLinesTo(columnsTo, entityColumns) {
|
|
1193
|
+
let linesTo = {
|
|
1194
|
+
add: {
|
|
1195
|
+
up: [],
|
|
1196
|
+
down: [],
|
|
1197
|
+
},
|
|
1198
|
+
drop: {
|
|
1199
|
+
up: [],
|
|
1200
|
+
down: [],
|
|
1201
|
+
},
|
|
1202
|
+
alter: {
|
|
1203
|
+
up: [],
|
|
1204
|
+
down: [],
|
|
1205
|
+
},
|
|
1206
|
+
};
|
|
1207
|
+
linesTo.add = {
|
|
1208
|
+
up: ["// add", ...this.genColumnDefinitions(columnsTo.add)],
|
|
1209
|
+
down: [
|
|
1210
|
+
"// rollback - add",
|
|
1211
|
+
`table.dropColumns(${columnsTo.add
|
|
1212
|
+
.map((col) => `'${col.name}'`)
|
|
1213
|
+
.join(", ")})`,
|
|
1214
|
+
],
|
|
1215
|
+
};
|
|
1216
|
+
linesTo.drop = {
|
|
1217
|
+
up: [
|
|
1218
|
+
"// drop",
|
|
1219
|
+
`table.dropColumns(${columnsTo.drop
|
|
1220
|
+
.map((col) => `'${col.name}'`)
|
|
1221
|
+
.join(", ")})`,
|
|
1222
|
+
],
|
|
1223
|
+
down: [
|
|
1224
|
+
"// rollback - drop",
|
|
1225
|
+
...this.genColumnDefinitions(columnsTo.drop),
|
|
1226
|
+
],
|
|
1227
|
+
};
|
|
1228
|
+
linesTo.alter = columnsTo.alter.reduce((r, dbColumn) => {
|
|
1229
|
+
const entityColumn = entityColumns.find((col) => col.name == dbColumn.name);
|
|
1230
|
+
if (entityColumn === undefined) {
|
|
1231
|
+
return r;
|
|
1232
|
+
}
|
|
1233
|
+
// 컬럼 변경사항
|
|
1234
|
+
const columnDiffUp = (0, lodash_1.difference)(this.genColumnDefinitions([entityColumn]), this.genColumnDefinitions([dbColumn]));
|
|
1235
|
+
const columnDiffDown = (0, lodash_1.difference)(this.genColumnDefinitions([dbColumn]), this.genColumnDefinitions([entityColumn]));
|
|
1236
|
+
if (columnDiffUp.length > 0) {
|
|
1237
|
+
r.up = [
|
|
1238
|
+
...r.up,
|
|
1239
|
+
"// alter column",
|
|
1240
|
+
...columnDiffUp.map((l) => l.replace(";", "") + ".alter();"),
|
|
1241
|
+
];
|
|
1242
|
+
r.down = [
|
|
1243
|
+
...r.down,
|
|
1244
|
+
"// rollback - alter column",
|
|
1245
|
+
...columnDiffDown.map((l) => l.replace(";", "") + ".alter();"),
|
|
1246
|
+
];
|
|
1247
|
+
}
|
|
1248
|
+
return r;
|
|
1249
|
+
}, {
|
|
1250
|
+
up: [],
|
|
1251
|
+
down: [],
|
|
1252
|
+
});
|
|
1253
|
+
return linesTo;
|
|
1254
|
+
}
|
|
1255
|
+
getAlterIndexesTo(entityIndexes, dbIndexes) {
|
|
1256
|
+
// 인덱스 비교
|
|
1257
|
+
let indexesTo = {
|
|
1258
|
+
add: [],
|
|
1259
|
+
drop: [],
|
|
1260
|
+
};
|
|
1261
|
+
const extraIndexes = {
|
|
1262
|
+
db: (0, lodash_1.differenceBy)(dbIndexes, entityIndexes, (col) => [col.type, col.columns.join("-")].join("//")),
|
|
1263
|
+
entity: (0, lodash_1.differenceBy)(entityIndexes, dbIndexes, (col) => [col.type, col.columns.join("-")].join("//")),
|
|
1264
|
+
};
|
|
1265
|
+
if (extraIndexes.entity.length > 0) {
|
|
1266
|
+
indexesTo.add = indexesTo.add.concat(extraIndexes.entity);
|
|
1267
|
+
}
|
|
1268
|
+
if (extraIndexes.db.length > 0) {
|
|
1269
|
+
indexesTo.drop = indexesTo.drop.concat(extraIndexes.db);
|
|
1270
|
+
}
|
|
1271
|
+
return indexesTo;
|
|
1272
|
+
}
|
|
1273
|
+
getAlterIndexLinesTo(indexesTo, columnsTo) {
|
|
1274
|
+
let linesTo = {
|
|
1275
|
+
add: {
|
|
1276
|
+
up: [],
|
|
1277
|
+
down: [],
|
|
1278
|
+
},
|
|
1279
|
+
drop: {
|
|
1280
|
+
up: [],
|
|
1281
|
+
down: [],
|
|
1282
|
+
},
|
|
1283
|
+
};
|
|
1284
|
+
// 인덱스가 추가되는 경우, 컬럼과 같이 추가된 케이스에는 drop에서 제외해야함!
|
|
1285
|
+
linesTo.add = {
|
|
1286
|
+
up: ["// add indexes", ...this.genIndexDefinitions(indexesTo.add)],
|
|
1287
|
+
down: [
|
|
1288
|
+
"// rollback - add indexes",
|
|
1289
|
+
...indexesTo.add
|
|
1290
|
+
.filter((index) => index.columns.every((colName) => columnsTo.add.map((col) => col.name).includes(colName)) === false)
|
|
1291
|
+
.map((index) => `table.drop${(0, inflection_1.capitalize)(index.type)}([${index.columns
|
|
1292
|
+
.map((columnName) => `'${columnName}'`)
|
|
1293
|
+
.join(",")}])`),
|
|
1294
|
+
],
|
|
1295
|
+
};
|
|
1296
|
+
// 인덱스가 삭제되는 경우, 컬럼과 같이 삭제된 케이스에는 drop에서 제외해야함!
|
|
1297
|
+
linesTo.drop = {
|
|
1298
|
+
up: [
|
|
1299
|
+
...indexesTo.drop
|
|
1300
|
+
.filter((index) => index.columns.every((colName) => columnsTo.drop.map((col) => col.name).includes(colName)) === false)
|
|
1301
|
+
.map((index) => `table.drop${(0, inflection_1.capitalize)(index.type)}([${index.columns
|
|
1302
|
+
.map((columnName) => `'${columnName}'`)
|
|
1303
|
+
.join(",")}])`),
|
|
1304
|
+
],
|
|
1305
|
+
down: [
|
|
1306
|
+
"// rollback - drop indexes",
|
|
1307
|
+
...this.genIndexDefinitions(indexesTo.drop),
|
|
1308
|
+
],
|
|
1309
|
+
};
|
|
1310
|
+
return linesTo;
|
|
1311
|
+
}
|
|
1312
|
+
generateAlterCode_Foreigns(table, entityForeigns, dbForeigns) {
|
|
1313
|
+
// console.log({ entityForeigns, dbForeigns });
|
|
1314
|
+
const getKey = (mf) => {
|
|
1315
|
+
return [mf.columns.join("-"), mf.to].join("///");
|
|
1316
|
+
};
|
|
1317
|
+
const fkTo = entityForeigns.reduce((result, entityF) => {
|
|
1318
|
+
const matchingDbF = dbForeigns.find((dbF) => getKey(entityF) === getKey(dbF));
|
|
1319
|
+
if (!matchingDbF) {
|
|
1320
|
+
result.add.push(entityF);
|
|
1321
|
+
return result;
|
|
1322
|
+
}
|
|
1323
|
+
if ((0, fast_deep_equal_1.default)(entityF, matchingDbF) === false) {
|
|
1324
|
+
result.alterSrc.push(matchingDbF);
|
|
1325
|
+
result.alterDst.push(entityF);
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
return result;
|
|
1329
|
+
}, {
|
|
1330
|
+
add: [],
|
|
1331
|
+
alterSrc: [],
|
|
1332
|
+
alterDst: [],
|
|
1333
|
+
});
|
|
1334
|
+
const linesTo = {
|
|
1335
|
+
add: this.genForeignDefinitions(table, fkTo.add),
|
|
1336
|
+
alterSrc: this.genForeignDefinitions(table, fkTo.alterSrc),
|
|
1337
|
+
alterDst: this.genForeignDefinitions(table, fkTo.alterDst),
|
|
1338
|
+
};
|
|
1339
|
+
const lines = [
|
|
1340
|
+
'import { Knex } from "knex";',
|
|
1341
|
+
"",
|
|
1342
|
+
"export async function up(knex: Knex): Promise<void> {",
|
|
1343
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1344
|
+
...linesTo.add.up,
|
|
1345
|
+
...linesTo.alterSrc.down,
|
|
1346
|
+
...linesTo.alterDst.up,
|
|
1347
|
+
"})",
|
|
1348
|
+
"}",
|
|
1349
|
+
"",
|
|
1350
|
+
"export async function down(knex: Knex): Promise<void> {",
|
|
1351
|
+
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1352
|
+
...linesTo.add.down,
|
|
1353
|
+
...linesTo.alterDst.down,
|
|
1354
|
+
...linesTo.alterSrc.up,
|
|
1355
|
+
"})",
|
|
1356
|
+
"}",
|
|
1357
|
+
];
|
|
1358
|
+
const formatted = prettier_1.default.format(lines.join("\n"), {
|
|
1359
|
+
parser: "typescript",
|
|
1360
|
+
});
|
|
1361
|
+
const title = [
|
|
1362
|
+
"alter",
|
|
1363
|
+
table,
|
|
1364
|
+
"foreigns",
|
|
1365
|
+
// TODO 바뀌는 부분
|
|
1366
|
+
].join("_");
|
|
1367
|
+
return [
|
|
1368
|
+
{
|
|
1369
|
+
table,
|
|
1370
|
+
title,
|
|
1371
|
+
formatted,
|
|
1372
|
+
type: "normal",
|
|
1373
|
+
},
|
|
1374
|
+
];
|
|
1375
|
+
}
|
|
1376
|
+
destroy() {
|
|
1377
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1378
|
+
yield Promise.all(this.targets.apply.map((db) => {
|
|
1379
|
+
return db.destroy();
|
|
1380
|
+
}));
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
exports.Migrator = Migrator;
|
|
1385
|
+
//# sourceMappingURL=migrator.js.map
|