sonamu 0.4.13 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +15 -0
- package/dist/api/base-frame.d.ts +8 -0
- package/dist/api/base-frame.d.ts.map +1 -0
- package/dist/api/base-frame.js +2 -0
- package/dist/api/base-frame.js.map +1 -0
- package/dist/api/caster.d.ts +5 -0
- package/dist/api/caster.d.ts.map +1 -0
- package/dist/api/caster.js +2 -0
- package/dist/api/caster.js.map +1 -0
- package/dist/api/code-converters.d.ts +23 -0
- package/dist/api/code-converters.d.ts.map +1 -0
- package/dist/api/code-converters.js +2 -0
- package/dist/api/code-converters.js.map +1 -0
- package/dist/api/context.d.ts +16 -0
- package/dist/api/context.d.ts.map +1 -0
- package/dist/api/context.js +2 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/decorators.d.ts +50 -0
- package/dist/api/decorators.d.ts.map +1 -0
- package/dist/api/decorators.js +2 -0
- package/dist/api/decorators.js.map +1 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/sonamu.d.ts +83 -0
- package/dist/api/sonamu.d.ts.map +1 -0
- package/dist/api/sonamu.js +2 -0
- package/dist/api/sonamu.js.map +1 -0
- package/dist/api/sonamu.types.d.ts +30 -0
- package/dist/api/sonamu.types.d.ts.map +1 -0
- package/dist/api/sonamu.types.js +2 -0
- package/dist/api/sonamu.types.js.map +1 -0
- package/dist/bin/build-config.d.ts +5 -0
- package/dist/bin/build-config.d.ts.map +1 -0
- package/dist/bin/build-config.js +2 -0
- package/dist/bin/build-config.js.map +1 -0
- package/dist/bin/cli-wrapper.d.ts +2 -0
- package/dist/bin/cli-wrapper.d.ts.map +1 -0
- package/dist/bin/cli-wrapper.js +1 -38
- package/dist/bin/cli-wrapper.js.map +1 -1
- package/dist/bin/cli.d.ts +2 -2
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +1 -903
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +2 -2
- package/dist/{chunk-DMJSNO2L.js → chunk-2WAC2GER.js} +44 -44
- package/dist/{chunk-DMJSNO2L.js.map → chunk-2WAC2GER.js.map} +1 -1
- package/dist/{chunk-NI37CY4T.mjs → chunk-C3IPIF6O.mjs} +2 -2
- package/dist/{chunk-DYFCACHD.js → chunk-EXHKSVTE.js} +7 -7
- package/dist/{chunk-QJFHDCBN.mjs → chunk-FCERKIIF.mjs} +2 -2
- package/dist/chunk-FCERKIIF.mjs.map +1 -0
- package/dist/{chunk-DDJ7T4MA.mjs → chunk-HGIBJYOU.mjs} +2 -2
- package/dist/{chunk-NIFOTHBW.mjs → chunk-JKSOJRQA.mjs} +2 -2
- package/dist/{chunk-CXAVBVKC.js → chunk-OTKKFP3Y.js} +100 -100
- package/dist/{chunk-J6S43O7G.js → chunk-UZ2IY5VE.js} +4 -4
- package/dist/database/_batch_update.d.ts +15 -0
- package/dist/database/_batch_update.d.ts.map +1 -0
- package/dist/database/_batch_update.js +2 -0
- package/dist/database/_batch_update.js.map +1 -0
- package/dist/database/base-model.d.ts +41 -0
- package/dist/database/base-model.d.ts.map +1 -0
- package/dist/database/base-model.js +2 -0
- package/dist/database/base-model.js.map +1 -0
- package/dist/database/code-generator.d.ts +13 -0
- package/dist/database/code-generator.d.ts.map +1 -0
- package/dist/database/code-generator.js +2 -0
- package/dist/database/code-generator.js.map +1 -0
- package/dist/database/db.d.ts +40 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +2 -0
- package/dist/database/db.js.map +1 -0
- package/dist/database/drivers/knex/base-model.js +8 -8
- package/dist/database/drivers/knex/base-model.mjs +3 -3
- package/dist/database/drivers/kysely/base-model.js +9 -9
- package/dist/database/drivers/kysely/base-model.mjs +3 -3
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts +2 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.d.ts.map +1 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.js +2 -0
- package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +1 -0
- package/dist/database/puri-wrapper.d.ts +34 -0
- package/dist/database/puri-wrapper.d.ts.map +1 -0
- package/dist/database/puri-wrapper.js +2 -0
- package/dist/database/puri-wrapper.js.map +1 -0
- package/dist/database/puri.d.ts +83 -0
- package/dist/database/puri.d.ts.map +1 -0
- package/dist/database/puri.js +2 -0
- package/dist/database/puri.js.map +1 -0
- package/dist/database/puri.types.d.ts +60 -0
- package/dist/database/puri.types.d.ts.map +1 -0
- package/dist/database/puri.types.js +2 -0
- package/dist/database/puri.types.js.map +1 -0
- package/dist/database/transaction-context.d.ts +9 -0
- package/dist/database/transaction-context.d.ts.map +1 -0
- package/dist/database/transaction-context.js +2 -0
- package/dist/database/transaction-context.js.map +1 -0
- package/dist/database/types.d.ts +39 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +2 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database/upsert-builder.d.ts +34 -0
- package/dist/database/upsert-builder.d.ts.map +1 -0
- package/dist/database/upsert-builder.js +2 -0
- package/dist/database/upsert-builder.js.map +1 -0
- package/dist/entity/entity-manager.d.ts +32 -0
- package/dist/entity/entity-manager.d.ts.map +1 -0
- package/dist/entity/entity-manager.js +2 -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 +2 -0
- package/dist/entity/entity-utils.js.map +1 -0
- package/dist/entity/entity.d.ts +62 -0
- package/dist/entity/entity.d.ts.map +1 -0
- package/dist/entity/entity.js +2 -0
- package/dist/entity/entity.js.map +1 -0
- package/dist/entity/migrator.d.ts +135 -0
- package/dist/entity/migrator.d.ts.map +1 -0
- package/dist/entity/migrator.js +2 -0
- package/dist/entity/migrator.js.map +1 -0
- package/dist/exceptions/error-handler.d.ts +3 -0
- package/dist/exceptions/error-handler.d.ts.map +1 -0
- package/dist/exceptions/error-handler.js +2 -0
- package/dist/exceptions/error-handler.js.map +1 -0
- package/dist/exceptions/so-exceptions.d.ts +48 -0
- package/dist/exceptions/so-exceptions.d.ts.map +1 -0
- package/dist/exceptions/so-exceptions.js +2 -0
- package/dist/exceptions/so-exceptions.js.map +1 -0
- package/dist/file-storage/driver.d.ts +45 -0
- package/dist/file-storage/driver.d.ts.map +1 -0
- package/dist/file-storage/driver.js +2 -0
- package/dist/file-storage/driver.js.map +1 -0
- package/dist/file-storage/file-storage.d.ts +50 -0
- package/dist/file-storage/file-storage.d.ts.map +1 -0
- package/dist/file-storage/file-storage.js +2 -0
- package/dist/file-storage/file-storage.js.map +1 -0
- package/dist/index.d.ts +22 -813
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/migration/code-generation.d.ts +15 -0
- package/dist/migration/code-generation.d.ts.map +1 -0
- package/dist/migration/code-generation.js +2 -0
- package/dist/migration/code-generation.js.map +1 -0
- package/dist/migration/migration-set.d.ts +17 -0
- package/dist/migration/migration-set.d.ts.map +1 -0
- package/dist/migration/migration-set.js +2 -0
- package/dist/migration/migration-set.js.map +1 -0
- package/dist/migration/migrator.d.ts +130 -0
- package/dist/migration/migrator.d.ts.map +1 -0
- package/dist/migration/migrator.js +2 -0
- package/dist/migration/migrator.js.map +1 -0
- package/dist/migration/types.d.ts +52 -0
- package/dist/migration/types.d.ts.map +1 -0
- package/dist/migration/types.js +2 -0
- package/dist/migration/types.js.map +1 -0
- package/dist/smd/smd-manager.d.ts +28 -0
- package/dist/smd/smd-manager.d.ts.map +1 -0
- package/dist/smd/smd-manager.js +2 -0
- package/dist/smd/smd-manager.js.map +1 -0
- package/dist/smd/smd.d.ts +40 -0
- package/dist/smd/smd.d.ts.map +1 -0
- package/dist/smd/smd.js +2 -0
- package/dist/smd/smd.js.map +1 -0
- package/dist/syncer/index.d.ts +2 -0
- package/dist/syncer/index.d.ts.map +1 -0
- package/dist/syncer/index.js +2 -0
- package/dist/syncer/index.js.map +1 -0
- package/dist/syncer/syncer.d.ts +127 -0
- package/dist/syncer/syncer.d.ts.map +1 -0
- package/dist/syncer/syncer.js +2 -0
- package/dist/syncer/syncer.js.map +1 -0
- package/dist/templates/base-template.d.ts +13 -0
- package/dist/templates/base-template.d.ts.map +1 -0
- package/dist/templates/base-template.js +2 -0
- package/dist/templates/base-template.js.map +1 -0
- 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 +2 -0
- package/dist/templates/entity.template.js.map +1 -0
- package/dist/templates/generated.template.d.ts +27 -0
- package/dist/templates/generated.template.d.ts.map +1 -0
- package/dist/templates/generated.template.js +2 -0
- package/dist/templates/generated.template.js.map +1 -0
- package/dist/templates/generated_http.template.d.ts +24 -0
- package/dist/templates/generated_http.template.d.ts.map +1 -0
- package/dist/templates/generated_http.template.js +2 -0
- package/dist/templates/generated_http.template.js.map +1 -0
- package/dist/templates/generated_sso.template.d.ts +20 -0
- package/dist/templates/generated_sso.template.d.ts.map +1 -0
- package/dist/templates/generated_sso.template.js +2 -0
- package/dist/templates/generated_sso.template.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/init_types.template.d.ts +17 -0
- package/dist/templates/init_types.template.d.ts.map +1 -0
- package/dist/templates/init_types.template.js +2 -0
- package/dist/templates/init_types.template.js.map +1 -0
- package/dist/templates/model.template.d.ts +17 -0
- package/dist/templates/model.template.d.ts.map +1 -0
- package/dist/templates/model.template.js +2 -0
- package/dist/templates/model.template.js.map +1 -0
- package/dist/templates/model_test.template.d.ts +17 -0
- package/dist/templates/model_test.template.d.ts.map +1 -0
- package/dist/templates/model_test.template.js +2 -0
- package/dist/templates/model_test.template.js.map +1 -0
- package/dist/templates/service.template.d.ts +29 -0
- package/dist/templates/service.template.d.ts.map +1 -0
- package/dist/templates/service.template.js +2 -0
- package/dist/templates/service.template.js.map +1 -0
- package/dist/templates/view_enums_buttonset.template.d.ts +17 -0
- package/dist/templates/view_enums_buttonset.template.d.ts.map +1 -0
- package/dist/templates/view_enums_buttonset.template.js +2 -0
- package/dist/templates/view_enums_buttonset.template.js.map +1 -0
- package/dist/templates/view_enums_dropdown.template.d.ts +18 -0
- package/dist/templates/view_enums_dropdown.template.d.ts.map +1 -0
- package/dist/templates/view_enums_dropdown.template.js +2 -0
- package/dist/templates/view_enums_dropdown.template.js.map +1 -0
- package/dist/templates/view_enums_select.template.d.ts +17 -0
- package/dist/templates/view_enums_select.template.d.ts.map +1 -0
- package/dist/templates/view_enums_select.template.js +2 -0
- package/dist/templates/view_enums_select.template.js.map +1 -0
- package/dist/templates/view_form.template.d.ts +26 -0
- package/dist/templates/view_form.template.d.ts.map +1 -0
- package/dist/templates/view_form.template.js +2 -0
- package/dist/templates/view_form.template.js.map +1 -0
- package/dist/templates/view_id_all_select.template.d.ts +17 -0
- package/dist/templates/view_id_all_select.template.d.ts.map +1 -0
- package/dist/templates/view_id_all_select.template.js +2 -0
- package/dist/templates/view_id_all_select.template.js.map +1 -0
- package/dist/templates/view_id_async_select.template.d.ts +17 -0
- package/dist/templates/view_id_async_select.template.d.ts.map +1 -0
- package/dist/templates/view_id_async_select.template.js +2 -0
- package/dist/templates/view_id_async_select.template.js.map +1 -0
- package/dist/templates/view_list.template.d.ts +38 -0
- package/dist/templates/view_list.template.d.ts.map +1 -0
- package/dist/templates/view_list.template.js +2 -0
- package/dist/templates/view_list.template.js.map +1 -0
- package/dist/templates/view_list_columns.template.d.ts +17 -0
- package/dist/templates/view_list_columns.template.d.ts.map +1 -0
- package/dist/templates/view_list_columns.template.js +2 -0
- package/dist/templates/view_list_columns.template.js.map +1 -0
- package/dist/templates/view_search_input.template.d.ts +17 -0
- package/dist/templates/view_search_input.template.d.ts.map +1 -0
- package/dist/templates/view_search_input.template.js +2 -0
- package/dist/templates/view_search_input.template.js.map +1 -0
- package/dist/testing/_relation-graph.d.ts +7 -0
- package/dist/testing/_relation-graph.d.ts.map +1 -0
- package/dist/testing/_relation-graph.js +2 -0
- package/dist/testing/_relation-graph.js.map +1 -0
- package/dist/testing/fixture-manager.d.ts +35 -0
- package/dist/testing/fixture-manager.d.ts.map +1 -0
- package/dist/testing/fixture-manager.js +2 -0
- package/dist/testing/fixture-manager.js.map +1 -0
- package/dist/types/types.d.ts +609 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +2 -0
- package/dist/types/types.js.map +1 -0
- package/dist/typings/knex.d.js +2 -0
- package/dist/typings/knex.d.js.map +1 -0
- package/dist/utils/async-utils.d.ts +25 -0
- package/dist/utils/async-utils.d.ts.map +1 -0
- package/dist/utils/async-utils.js +2 -0
- package/dist/utils/async-utils.js.map +1 -0
- package/dist/utils/controller.d.ts +9 -0
- package/dist/utils/controller.d.ts.map +1 -0
- package/dist/utils/controller.js +2 -0
- package/dist/utils/controller.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +9 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +2 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/lodash-able.d.ts +2 -0
- package/dist/utils/lodash-able.d.ts.map +1 -0
- package/dist/utils/lodash-able.js +2 -0
- package/dist/utils/lodash-able.js.map +1 -0
- package/dist/utils/model.d.ts +17 -0
- package/dist/utils/model.d.ts.map +1 -0
- package/dist/utils/model.js +2 -0
- package/dist/utils/model.js.map +1 -0
- package/dist/utils/sql-parser.d.ts +4 -0
- package/dist/utils/sql-parser.d.ts.map +1 -0
- package/dist/utils/sql-parser.js +2 -0
- package/dist/utils/sql-parser.js.map +1 -0
- package/dist/utils/utils.d.ts +9 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +2 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/utils/zod-error.d.ts +8 -0
- package/dist/utils/zod-error.d.ts.map +1 -0
- package/dist/utils/zod-error.js +2 -0
- package/dist/utils/zod-error.js.map +1 -0
- package/nodemon.json +6 -0
- package/package.json +29 -44
- package/src/api/base-frame.ts +3 -4
- package/src/api/caster.ts +22 -23
- package/src/api/code-converters.ts +170 -134
- package/src/api/context.ts +13 -6
- package/src/api/decorators.ts +146 -20
- package/src/api/index.ts +2 -0
- package/src/api/sonamu.ts +374 -165
- package/src/bin/build-config.ts +5 -0
- package/src/bin/cli-wrapper.ts +29 -40
- package/src/bin/cli.ts +132 -190
- package/src/database/_batch_update.ts +10 -15
- package/src/database/base-model.ts +300 -216
- package/src/database/db.ts +191 -21
- package/src/database/{drivers/knex/plugins → knex-plugins}/knex-on-duplicate-update.ts +1 -1
- package/src/database/puri-wrapper.ts +129 -0
- package/src/database/puri.ts +808 -0
- package/src/database/puri.types.ts +222 -0
- package/src/database/transaction-context.ts +18 -0
- package/src/database/upsert-builder.ts +32 -35
- package/src/entity/entity-manager.ts +7 -15
- package/src/entity/entity.ts +9 -31
- package/src/entity/migrator-/354/235/264/354/202/254/352/260/224/354/226/264/354/232/224.md +1 -0
- package/src/file-storage/driver.ts +121 -0
- package/src/file-storage/file-storage.ts +100 -0
- package/src/index.ts +14 -11
- package/src/migration/code-generation.ts +777 -0
- package/src/migration/migration-set.ts +453 -0
- package/src/migration/migrator.ts +823 -0
- package/src/migration/types.ts +53 -0
- package/src/shared/web.shared.ts.txt +33 -2
- package/src/syncer/syncer.ts +294 -127
- package/src/templates/generated.template.ts +13 -1
- package/src/templates/generated_http.template.ts +15 -12
- package/src/templates/generated_sso.template.ts +50 -2
- package/src/templates/model.template.ts +138 -2
- package/src/templates/service.template.ts +0 -1
- package/src/templates/view_form.template.ts +11 -7
- package/src/templates/view_list.template.ts +12 -4
- package/src/testing/fixture-manager.ts +229 -174
- package/src/types/types.ts +102 -14
- package/src/utils/async-utils.ts +64 -0
- package/src/utils/fs-utils.ts +17 -0
- package/src/utils/model.ts +0 -2
- package/src/utils/utils.ts +14 -58
- package/src/utils/zod-error.ts +12 -176
- package/tsconfig.json +2 -0
- package/tsup.config.js +4 -2
- package/.pnp.cjs +0 -14363
- package/.pnp.loader.mjs +0 -2047
- package/.vscode/extensions.json +0 -6
- package/.vscode/settings.json +0 -9
- package/.yarnrc.yml +0 -5
- package/dist/chunk-QJFHDCBN.mjs.map +0 -1
- package/src/database/base-model.abstract.ts +0 -97
- package/src/database/db.abstract.ts +0 -75
- package/src/database/drivers/knex/base-model.ts +0 -55
- package/src/database/drivers/knex/client.ts +0 -209
- package/src/database/drivers/knex/db.ts +0 -232
- package/src/database/drivers/knex/generator.ts +0 -659
- package/src/database/drivers/kysely/base-model.ts +0 -89
- package/src/database/drivers/kysely/client.ts +0 -309
- package/src/database/drivers/kysely/db.ts +0 -238
- package/src/database/drivers/kysely/generator.ts +0 -714
- package/src/database/types.ts +0 -118
- package/src/entity/migrator.ts +0 -1400
- package/src/smd/smd-manager.ts +0 -139
- package/src/smd/smd.ts +0 -571
- package/src/templates/kysely_types.template.ts +0 -205
- /package/dist/{chunk-NI37CY4T.mjs.map → chunk-C3IPIF6O.mjs.map} +0 -0
- /package/dist/{chunk-DYFCACHD.js.map → chunk-EXHKSVTE.js.map} +0 -0
- /package/dist/{chunk-DDJ7T4MA.mjs.map → chunk-HGIBJYOU.mjs.map} +0 -0
- /package/dist/{chunk-NIFOTHBW.mjs.map → chunk-JKSOJRQA.mjs.map} +0 -0
- /package/dist/{chunk-CXAVBVKC.js.map → chunk-OTKKFP3Y.js.map} +0 -0
- /package/dist/{chunk-J6S43O7G.js.map → chunk-UZ2IY5VE.js.map} +0 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import knex, { Knex } from "knex";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { DateTime } from "luxon";
|
|
5
|
+
import { mkdir, readdir, unlink, writeFile } from "fs/promises";
|
|
6
|
+
import { exists } from "../utils/fs-utils";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { GenMigrationCode, MigrationSet } from "../types/types";
|
|
11
|
+
import { EntityManager } from "../entity/entity-manager";
|
|
12
|
+
import { Sonamu } from "../api";
|
|
13
|
+
import { ServiceUnavailableException } from "../exceptions/so-exceptions";
|
|
14
|
+
import { SonamuDBConfig } from "../database/db";
|
|
15
|
+
import { generateCreateCode, generateAlterCode } from "./code-generation";
|
|
16
|
+
import { MigrationStatus, MigrationCode, ConnString } from "./types";
|
|
17
|
+
import { getMigrationSetFromDB } from "./migration-set";
|
|
18
|
+
import { getMigrationSetFromEntity } from "./migration-set";
|
|
19
|
+
|
|
20
|
+
type MigratorMode = "dev" | "deploy";
|
|
21
|
+
export type MigratorOptions = {
|
|
22
|
+
readonly mode: MigratorMode;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class Migrator {
|
|
26
|
+
targets: {
|
|
27
|
+
compare?: Knex;
|
|
28
|
+
pending: Knex;
|
|
29
|
+
shadow: Knex;
|
|
30
|
+
apply: Knex[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
constructor(private readonly options: MigratorOptions) {
|
|
34
|
+
const { dbConfig } = Sonamu;
|
|
35
|
+
|
|
36
|
+
if (this.options.mode === "dev") {
|
|
37
|
+
const devDB = knex(dbConfig.development_master);
|
|
38
|
+
const testDB = knex(dbConfig.test);
|
|
39
|
+
const fixtureLocalDB = knex(dbConfig.fixture_local);
|
|
40
|
+
|
|
41
|
+
const applyDBs = [devDB, testDB, fixtureLocalDB];
|
|
42
|
+
if (
|
|
43
|
+
(dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
|
|
44
|
+
.host !==
|
|
45
|
+
(dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
|
|
46
|
+
.host ||
|
|
47
|
+
(dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
|
|
48
|
+
.database !==
|
|
49
|
+
(dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
|
|
50
|
+
.database
|
|
51
|
+
) {
|
|
52
|
+
const fixtureRemoteDB = knex(dbConfig.fixture_remote);
|
|
53
|
+
applyDBs.push(fixtureRemoteDB);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.targets = {
|
|
57
|
+
compare: devDB,
|
|
58
|
+
pending: devDB,
|
|
59
|
+
shadow: testDB,
|
|
60
|
+
apply: applyDBs,
|
|
61
|
+
};
|
|
62
|
+
} else if (this.options.mode === "deploy") {
|
|
63
|
+
const productionDB = knex(dbConfig.production_master);
|
|
64
|
+
const testDB = knex(dbConfig.test);
|
|
65
|
+
|
|
66
|
+
this.targets = {
|
|
67
|
+
pending: productionDB,
|
|
68
|
+
shadow: testDB,
|
|
69
|
+
apply: [productionDB],
|
|
70
|
+
};
|
|
71
|
+
} else {
|
|
72
|
+
throw new Error(`잘못된 모드 ${this.options.mode} 입력`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async getMigrationCodes(): Promise<{
|
|
77
|
+
normal: MigrationCode[];
|
|
78
|
+
onlyTs: MigrationCode[];
|
|
79
|
+
onlyJs: MigrationCode[];
|
|
80
|
+
}> {
|
|
81
|
+
const srcMigrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
82
|
+
const distMigrationsDir = `${Sonamu.apiRootPath}/dist/migrations`;
|
|
83
|
+
|
|
84
|
+
if (!(await exists(srcMigrationsDir))) {
|
|
85
|
+
await mkdir(srcMigrationsDir, {
|
|
86
|
+
recursive: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!(await exists(distMigrationsDir))) {
|
|
90
|
+
await mkdir(distMigrationsDir, {
|
|
91
|
+
recursive: true,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const srcMigrations = (await readdir(srcMigrationsDir))
|
|
95
|
+
.filter((f) => f.endsWith(".ts"))
|
|
96
|
+
.map((f) => f.split(".")[0]);
|
|
97
|
+
const distMigrations = (await readdir(distMigrationsDir))
|
|
98
|
+
.filter((f) => f.endsWith(".js"))
|
|
99
|
+
.map((f) => f.split(".")[0]);
|
|
100
|
+
|
|
101
|
+
const normal = _.intersection(srcMigrations, distMigrations)
|
|
102
|
+
.map((filename) => {
|
|
103
|
+
return {
|
|
104
|
+
name: filename,
|
|
105
|
+
path: path.join(srcMigrationsDir, filename) + ".ts",
|
|
106
|
+
};
|
|
107
|
+
})
|
|
108
|
+
.sort((a, b) => (a > b ? 1 : -1));
|
|
109
|
+
|
|
110
|
+
const onlyTs = _.difference(srcMigrations, distMigrations).map(
|
|
111
|
+
(filename) => {
|
|
112
|
+
return {
|
|
113
|
+
name: filename,
|
|
114
|
+
path: path.join(srcMigrationsDir, filename) + ".ts",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const onlyJs = _.difference(distMigrations, srcMigrations).map(
|
|
120
|
+
(filename) => {
|
|
121
|
+
return {
|
|
122
|
+
name: filename,
|
|
123
|
+
path: path.join(distMigrationsDir, filename) + ".js",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
normal,
|
|
130
|
+
onlyTs,
|
|
131
|
+
onlyJs,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 타겟별 마이그레이션 상태와 코드 생성/준비 상태를 구해옵니다.
|
|
137
|
+
* 실제로 DB에 접근도 하고 마이그레이션 코드 파일도 확인하고,
|
|
138
|
+
* 필요하다면 적용할 수 있는 코드를 생성까지 해옵니다.
|
|
139
|
+
*
|
|
140
|
+
* CLI와 Sonamu UI에서 사용됩니다.
|
|
141
|
+
*
|
|
142
|
+
* @returns
|
|
143
|
+
*/
|
|
144
|
+
async getStatus(): Promise<MigrationStatus> {
|
|
145
|
+
const { normal, onlyTs, onlyJs } = await this.getMigrationCodes();
|
|
146
|
+
if (onlyTs.length > 0) {
|
|
147
|
+
console.debug({ onlyTs });
|
|
148
|
+
throw new ServiceUnavailableException(
|
|
149
|
+
`There are un-compiled TS migration files.\nPlease compile them first. You might want to run a development server with HMR.\n\n${onlyTs
|
|
150
|
+
.map((f) => f.name)
|
|
151
|
+
.join("\n")}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (onlyJs.length > 0) {
|
|
155
|
+
console.debug({ onlyJs });
|
|
156
|
+
await Promise.all(
|
|
157
|
+
onlyJs.map(async (f) => {
|
|
158
|
+
execSync(
|
|
159
|
+
`rm -f ${f.path.replace("/src/", "/dist/").replace(".ts", ".js")}`
|
|
160
|
+
);
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const connKeys = Object.keys(Sonamu.dbConfig).filter(
|
|
166
|
+
(key) => key.endsWith("_slave") === false
|
|
167
|
+
) as (keyof typeof Sonamu.dbConfig)[];
|
|
168
|
+
|
|
169
|
+
const statuses = await Promise.all(
|
|
170
|
+
connKeys.map(async (connKey) => {
|
|
171
|
+
const knexOptions = Sonamu.dbConfig[connKey];
|
|
172
|
+
const tConn = knex(knexOptions);
|
|
173
|
+
|
|
174
|
+
const status = await (async () => {
|
|
175
|
+
try {
|
|
176
|
+
return await tConn.migrate.status();
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.warn(
|
|
179
|
+
chalk.yellow(
|
|
180
|
+
`${connKey}의 마이그레이션 상태를 가져오는 데에 실패하였습니다. 데이터베이스가 올바르게 구성되지 않은 것 같습니다. 확인하시고 다시 시도해주세요.\n시도한 연결 설정:\n${JSON.stringify(knexOptions.connection, null, 2)}\n발생한 에러:\n${err}\n`
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
return "error" /*클라이언트에서 에러 체크에 사용하는 리터럴입니다.*/;
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
const pending = await (async () => {
|
|
187
|
+
try {
|
|
188
|
+
const [, fdList] = await tConn.migrate.list();
|
|
189
|
+
return fdList.map((fd: { file: string }) =>
|
|
190
|
+
fd.file.replace(".js", "")
|
|
191
|
+
);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
const currentVersion = await (async () => {
|
|
197
|
+
try {
|
|
198
|
+
return await tConn.migrate.currentVersion();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return "error";
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
203
|
+
|
|
204
|
+
const connection =
|
|
205
|
+
knexOptions.connection as Knex.MySql2ConnectionConfig;
|
|
206
|
+
|
|
207
|
+
await tConn.destroy();
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
name: connKey.replace("_master", ""),
|
|
211
|
+
connKey,
|
|
212
|
+
connString: `mysql2://${connection.user ?? ""}@${connection.host}:${
|
|
213
|
+
connection.port
|
|
214
|
+
}/${connection.database}` as ConnString,
|
|
215
|
+
currentVersion,
|
|
216
|
+
status,
|
|
217
|
+
pending,
|
|
218
|
+
};
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const preparedCodes: GenMigrationCode[] = await (async () => {
|
|
223
|
+
const status0conn = statuses.find((status) => status.status === 0);
|
|
224
|
+
if (status0conn === undefined) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const compareDBconn = knex(Sonamu.dbConfig[status0conn.connKey]);
|
|
229
|
+
const genCodes = await this.compareMigrations(compareDBconn);
|
|
230
|
+
|
|
231
|
+
await compareDBconn.destroy();
|
|
232
|
+
|
|
233
|
+
return genCodes;
|
|
234
|
+
})();
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
conns: statuses,
|
|
238
|
+
codes: normal,
|
|
239
|
+
preparedCodes,
|
|
240
|
+
};
|
|
241
|
+
/*
|
|
242
|
+
TS/JS 코드 컴파일 상태 확인
|
|
243
|
+
1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
|
|
244
|
+
2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
|
|
245
|
+
|
|
246
|
+
DB 마이그레이션 상태 확인
|
|
247
|
+
1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
|
|
248
|
+
- connKey: string
|
|
249
|
+
- status: number
|
|
250
|
+
- currentVersion: string
|
|
251
|
+
- list: { file: string; directory: string }[]
|
|
252
|
+
|
|
253
|
+
*/
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 마이그레이션을 적용하거나 롤백합니다.
|
|
258
|
+
* Sonamu UI에서 마이그레이션 작업을 수행할 때 사용됩니다.
|
|
259
|
+
*
|
|
260
|
+
* CLI와 Sonamu UI에서 사용됩니다.
|
|
261
|
+
*
|
|
262
|
+
* @param action 작업 유형 (apply/rollback)
|
|
263
|
+
* @param targets 작업 대상 DB 설정 키 (keyof SonamuDBConfig)
|
|
264
|
+
* @returns 작업 결과
|
|
265
|
+
*/
|
|
266
|
+
async runAction(
|
|
267
|
+
action: "apply" | "rollback",
|
|
268
|
+
targets: (keyof SonamuDBConfig)[]
|
|
269
|
+
): Promise<
|
|
270
|
+
{
|
|
271
|
+
connKey: string;
|
|
272
|
+
batchNo: number;
|
|
273
|
+
applied: string[];
|
|
274
|
+
}[]
|
|
275
|
+
> {
|
|
276
|
+
// get uniq knex configs
|
|
277
|
+
const configs = _.uniqBy(
|
|
278
|
+
targets
|
|
279
|
+
.map((target) => ({
|
|
280
|
+
connKey: target,
|
|
281
|
+
options: Sonamu.dbConfig[target as keyof typeof Sonamu.dbConfig],
|
|
282
|
+
}))
|
|
283
|
+
.filter((c) => c.options !== undefined),
|
|
284
|
+
({ options }) =>
|
|
285
|
+
`${(options.connection as Knex.MySql2ConnectionConfig).host}:${
|
|
286
|
+
(options.connection as Knex.MySql2ConnectionConfig).port ?? 3306
|
|
287
|
+
}/${(options.connection as Knex.MySql2ConnectionConfig).database}`
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// get connections
|
|
291
|
+
const conns = await Promise.all(
|
|
292
|
+
configs.map(async (config) => ({
|
|
293
|
+
connKey: config.connKey,
|
|
294
|
+
knex: knex(config.options),
|
|
295
|
+
}))
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// action
|
|
299
|
+
const result = await (async () => {
|
|
300
|
+
switch (action) {
|
|
301
|
+
case "apply":
|
|
302
|
+
return Promise.all(
|
|
303
|
+
conns.map(async ({ connKey, knex }) => {
|
|
304
|
+
const [batchNo, applied] = await knex.migrate.latest();
|
|
305
|
+
return {
|
|
306
|
+
connKey,
|
|
307
|
+
batchNo,
|
|
308
|
+
applied,
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
case "rollback":
|
|
313
|
+
return Promise.all(
|
|
314
|
+
conns.map(async ({ connKey, knex }) => {
|
|
315
|
+
const [batchNo, applied] = await knex.migrate.rollback();
|
|
316
|
+
return {
|
|
317
|
+
connKey,
|
|
318
|
+
batchNo,
|
|
319
|
+
applied,
|
|
320
|
+
};
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
325
|
+
|
|
326
|
+
// destroy
|
|
327
|
+
await Promise.all(
|
|
328
|
+
conns.map(({ knex }) => {
|
|
329
|
+
return knex.destroy();
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 마이그레이션 코드 파일을 삭제합니다.
|
|
338
|
+
*
|
|
339
|
+
* Sonamu UI에서 사용됩니다.
|
|
340
|
+
*
|
|
341
|
+
* @param codeNames 삭제할 마이그레이션 코드 파일 이름 배열
|
|
342
|
+
* @returns 삭제된 마이그레이션 코드 파일 개수
|
|
343
|
+
*/
|
|
344
|
+
async delCodes(codeNames: string[]): Promise<number> {
|
|
345
|
+
const { conns } = await this.getStatus();
|
|
346
|
+
if (
|
|
347
|
+
conns.some((conn) => {
|
|
348
|
+
return codeNames.some(
|
|
349
|
+
(codeName) => conn.pending.includes(codeName) === false
|
|
350
|
+
);
|
|
351
|
+
})
|
|
352
|
+
) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
"You cannot delete a migration file if there is already applied."
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const delFiles = codeNames
|
|
359
|
+
.map((codeName) => [
|
|
360
|
+
`${Sonamu.apiRootPath}/src/migrations/${codeName}.ts`,
|
|
361
|
+
`${Sonamu.apiRootPath}/dist/migrations/${codeName}.js`,
|
|
362
|
+
])
|
|
363
|
+
.flat();
|
|
364
|
+
|
|
365
|
+
const res = await Promise.all(
|
|
366
|
+
delFiles.map(async (delFile) => {
|
|
367
|
+
if (await exists(delFile)) {
|
|
368
|
+
console.log(chalk.red(`DELETE: ${delFile}`));
|
|
369
|
+
await unlink(delFile);
|
|
370
|
+
return delFiles.includes(".ts") ? 1 : 0;
|
|
371
|
+
}
|
|
372
|
+
return 0;
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
return _.sum(res);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 마이그레이션 코드 파일을 생성합니다.
|
|
380
|
+
*
|
|
381
|
+
* Sonamu UI에서 사용됩니다.
|
|
382
|
+
*
|
|
383
|
+
* @returns 생성된 마이그레이션 코드 파일 개수
|
|
384
|
+
*/
|
|
385
|
+
async generatePreparedCodes(): Promise<number> {
|
|
386
|
+
const { preparedCodes } = await this.getStatus();
|
|
387
|
+
if (preparedCodes.length === 0) {
|
|
388
|
+
console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 실제 코드 생성
|
|
393
|
+
const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
394
|
+
|
|
395
|
+
for (const [index, pcode] of preparedCodes.entries()) {
|
|
396
|
+
if (pcode.formatted) {
|
|
397
|
+
const dateTag = DateTime.local()
|
|
398
|
+
.plus({ seconds: index })
|
|
399
|
+
.toFormat("yyyyMMddHHmmss");
|
|
400
|
+
const filePath = `${migrationsDir}/${dateTag}_${pcode.title}.ts`;
|
|
401
|
+
await writeFile(filePath, pcode.formatted!);
|
|
402
|
+
console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return preparedCodes.length;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* pending 마이그레이션 목록을 삭제합니다.
|
|
411
|
+
*
|
|
412
|
+
* CLI에서 사용됩니다.
|
|
413
|
+
*/
|
|
414
|
+
async clearPendingList(): Promise<void> {
|
|
415
|
+
const [, pendingList] = (await this.targets.pending.migrate.list()) as [
|
|
416
|
+
unknown,
|
|
417
|
+
{
|
|
418
|
+
file: string;
|
|
419
|
+
directory: string;
|
|
420
|
+
}[],
|
|
421
|
+
];
|
|
422
|
+
const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
423
|
+
const delList = pendingList.map((df) => {
|
|
424
|
+
return path.join(migrationsDir, df.file).replace(".js", ".ts");
|
|
425
|
+
});
|
|
426
|
+
for (let p of delList) {
|
|
427
|
+
if (await exists(p)) {
|
|
428
|
+
await unlink(p);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
await this.cleanUpDist(true);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 마이그레이션 코드 파일을 확인합니다.
|
|
436
|
+
*
|
|
437
|
+
* CLI에서 사용됩니다.
|
|
438
|
+
*/
|
|
439
|
+
async check(): Promise<void> {
|
|
440
|
+
const codes = await this.compareMigrations(this.targets.compare!);
|
|
441
|
+
if (codes.length === 0) {
|
|
442
|
+
console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 현재 생성된 코드 표기
|
|
447
|
+
console.table(codes, ["type", "title"]);
|
|
448
|
+
console.log(codes[0]);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 마이그레이션을 수행합니다.
|
|
453
|
+
*
|
|
454
|
+
* runAction이 인자로 들어온 타겟들에 대해 주어진 동작(apply/rollback)을 수행한다면,
|
|
455
|
+
* 이 함수는 생성자로 들어온 connection(knex)들에 대해 마이그레이션을 수행합니다.
|
|
456
|
+
*
|
|
457
|
+
* CLI에서 사용됩니다.
|
|
458
|
+
*/
|
|
459
|
+
async run(): Promise<void> {
|
|
460
|
+
// pending 마이그레이션 확인
|
|
461
|
+
const [, pendingList] = await this.targets.pending.migrate.list();
|
|
462
|
+
if (pendingList.length > 0) {
|
|
463
|
+
console.log(
|
|
464
|
+
chalk.red("pending 된 마이그레이션이 존재합니다."),
|
|
465
|
+
pendingList.map((pending: any) => pending.file)
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
// pending이 있는 경우 Shadow DB 테스트 진행 여부 컨펌
|
|
469
|
+
const answer = await prompts({
|
|
470
|
+
type: "confirm",
|
|
471
|
+
name: "value",
|
|
472
|
+
message: "Shadow DB 테스트를 진행하시겠습니까?",
|
|
473
|
+
initial: true,
|
|
474
|
+
});
|
|
475
|
+
if (answer.value === false) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.time(chalk.blue("Migrator - runShadowTest"));
|
|
480
|
+
await this.runShadowTest();
|
|
481
|
+
console.timeEnd(chalk.blue("Migrator - runShadowTest"));
|
|
482
|
+
await Promise.all(
|
|
483
|
+
this.targets.apply.map(async (applyDb) => {
|
|
484
|
+
const label = chalk.green(
|
|
485
|
+
`APPLIED ${
|
|
486
|
+
applyDb.client.connectionSettings.host
|
|
487
|
+
} ${applyDb.client.database()}`
|
|
488
|
+
);
|
|
489
|
+
console.time(label);
|
|
490
|
+
const [,] = await applyDb.migrate.latest();
|
|
491
|
+
console.timeEnd(label);
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Entity-DB간 비교하여 코드 생성 리턴
|
|
497
|
+
const codes = await this.compareMigrations(this.targets.compare!);
|
|
498
|
+
if (codes.length === 0) {
|
|
499
|
+
console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 현재 생성된 코드 표기
|
|
504
|
+
console.table(codes, ["type", "title"]);
|
|
505
|
+
|
|
506
|
+
/* DEBUG: 디버깅용 코드
|
|
507
|
+
codes.map((code) => console.log(code.formatted));
|
|
508
|
+
process.exit();
|
|
509
|
+
*/
|
|
510
|
+
|
|
511
|
+
// 실제 파일 생성 프롬프트
|
|
512
|
+
const answer = await prompts({
|
|
513
|
+
type: "confirm",
|
|
514
|
+
name: "value",
|
|
515
|
+
message: "마이그레이션 코드를 생성하시겠습니까?",
|
|
516
|
+
initial: false,
|
|
517
|
+
});
|
|
518
|
+
if (answer.value === false) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 실제 코드 생성
|
|
523
|
+
const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
524
|
+
|
|
525
|
+
for (const [index, code] of codes.entries()) {
|
|
526
|
+
if (code.formatted) {
|
|
527
|
+
const dateTag = DateTime.local()
|
|
528
|
+
.plus({ seconds: index })
|
|
529
|
+
.toFormat("yyyyMMddHHmmss");
|
|
530
|
+
const filePath = `${migrationsDir}/${dateTag}_${code.title}.ts`;
|
|
531
|
+
await writeFile(filePath, code.formatted!);
|
|
532
|
+
console.log(chalk.green(`MIGRTAION CREATED ${filePath}`));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 타겟으로 지정된 DB를 롤백합니다.
|
|
539
|
+
*
|
|
540
|
+
* runAction이 인자로 들어온 타겟들에 대해 주어진 동작(apply/rollback)을 수행한다면,
|
|
541
|
+
* 이 함수는 생성자로 들어온 connection(knex)들에 대해 롤백을 수행합니다.
|
|
542
|
+
*
|
|
543
|
+
* CLI에서 사용됩니다.
|
|
544
|
+
*/
|
|
545
|
+
async rollback() {
|
|
546
|
+
console.time(chalk.red("rollback:"));
|
|
547
|
+
const rollbackAllResult = await Promise.all(
|
|
548
|
+
this.targets.apply.map(async (db) => {
|
|
549
|
+
await db.migrate.forceFreeMigrationsLock();
|
|
550
|
+
return db.migrate.rollback(undefined, false);
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
console.dir({ rollbackAllResult }, { depth: null });
|
|
554
|
+
console.timeEnd(chalk.red("rollback:"));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* 빌드된 마이그레이션 파일을 삭제합니다.
|
|
559
|
+
*
|
|
560
|
+
* CLI에서 사용됩니다.
|
|
561
|
+
*
|
|
562
|
+
* @param force 강제 삭제 여부
|
|
563
|
+
* @returns
|
|
564
|
+
*/
|
|
565
|
+
async cleanUpDist(force: boolean = false): Promise<void> {
|
|
566
|
+
async function getFilesUnder(dir: string): Promise<string[]> {
|
|
567
|
+
const migrationPath = path.join(Sonamu.apiRootPath, dir, "migrations");
|
|
568
|
+
if (!(await exists(migrationPath))) {
|
|
569
|
+
await mkdir(migrationPath, {
|
|
570
|
+
recursive: true,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return (await readdir(migrationPath)).filter(
|
|
574
|
+
(filename) => filename.startsWith(".") === false
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const files = {
|
|
579
|
+
src: await getFilesUnder("src"),
|
|
580
|
+
dist: await getFilesUnder("dist"),
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const diffOnSrc = _.differenceBy(
|
|
584
|
+
files.src,
|
|
585
|
+
files.dist,
|
|
586
|
+
(filename) => filename.split(".")[0]
|
|
587
|
+
);
|
|
588
|
+
if (diffOnSrc.length > 0) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
"컴파일 되지 않은 파일이 있습니다.\n" + diffOnSrc.join("\n")
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const diffOnDist = _.differenceBy(
|
|
595
|
+
files.dist,
|
|
596
|
+
files.src,
|
|
597
|
+
(filename) => filename.split(".")[0]
|
|
598
|
+
);
|
|
599
|
+
if (diffOnDist.length > 0) {
|
|
600
|
+
console.log(chalk.red("원본 ts파일을 찾을 수 없는 js파일이 있습니다."));
|
|
601
|
+
console.log(diffOnDist);
|
|
602
|
+
|
|
603
|
+
if (!force) {
|
|
604
|
+
const answer = await prompts({
|
|
605
|
+
type: "confirm",
|
|
606
|
+
name: "value",
|
|
607
|
+
message: "삭제를 진행하시겠습니까?",
|
|
608
|
+
initial: true,
|
|
609
|
+
});
|
|
610
|
+
if (answer.value === false) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const filesToRm = diffOnDist.map((filename) => {
|
|
616
|
+
return path.join(Sonamu.apiRootPath, "dist", "migrations", filename);
|
|
617
|
+
});
|
|
618
|
+
for (const filePath of filesToRm) {
|
|
619
|
+
await unlink(filePath);
|
|
620
|
+
}
|
|
621
|
+
console.log(chalk.green(`${filesToRm.length}건 삭제되었습니다!`));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Shadow DB 테스트를 진행합니다.
|
|
627
|
+
*
|
|
628
|
+
* Sonamu UI에서 사용됩니다.
|
|
629
|
+
*
|
|
630
|
+
* @returns Shadow DB 테스트 결과
|
|
631
|
+
*/
|
|
632
|
+
async runShadowTest(): Promise<
|
|
633
|
+
{
|
|
634
|
+
connKey: string;
|
|
635
|
+
batchNo: number;
|
|
636
|
+
applied: string[];
|
|
637
|
+
}[]
|
|
638
|
+
> {
|
|
639
|
+
// ShadowDB 생성 후 테스트 진행
|
|
640
|
+
const tdb = knex(Sonamu.dbConfig.test);
|
|
641
|
+
const tdbConn = Sonamu.dbConfig.test
|
|
642
|
+
.connection as Knex.MySql2ConnectionConfig;
|
|
643
|
+
const shadowDatabase = tdbConn.database + "__migration_shadow";
|
|
644
|
+
const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
|
|
645
|
+
|
|
646
|
+
// 테스트DB 덤프 후 Database명 치환
|
|
647
|
+
console.log(
|
|
648
|
+
chalk.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`)
|
|
649
|
+
);
|
|
650
|
+
execSync(
|
|
651
|
+
`mysqldump -h${tdbConn.host} -P${tdbConn.port ?? 3306} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
|
|
652
|
+
);
|
|
653
|
+
execSync(
|
|
654
|
+
`sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
// 기존 ShadowDB 리셋
|
|
658
|
+
console.log(chalk.magenta(`${shadowDatabase} 리셋`));
|
|
659
|
+
await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
|
|
660
|
+
await tdb.raw(`CREATE DATABASE \`${shadowDatabase}\`;`);
|
|
661
|
+
|
|
662
|
+
// ShadowDB 테이블 + 데이터 생성
|
|
663
|
+
console.log(chalk.magenta(`${shadowDatabase} 데이터베이스 생성`));
|
|
664
|
+
execSync(
|
|
665
|
+
`mysql -h${tdbConn.host} -P${tdbConn.port ?? 3306} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
// shadow db 테스트 진행
|
|
669
|
+
const sdb = knex({
|
|
670
|
+
...Sonamu.dbConfig.test,
|
|
671
|
+
connection: {
|
|
672
|
+
...tdbConn,
|
|
673
|
+
database: shadowDatabase,
|
|
674
|
+
password: tdbConn.password,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// shadow db 테스트 진행
|
|
679
|
+
try {
|
|
680
|
+
const [batchNo, applied] = await sdb.migrate.latest();
|
|
681
|
+
console.log(chalk.green("Shadow DB 테스트에 성공했습니다!"), {
|
|
682
|
+
batchNo,
|
|
683
|
+
applied,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// 생성한 Shadow DB 삭제
|
|
687
|
+
console.log(chalk.magenta(`${shadowDatabase} 삭제`));
|
|
688
|
+
await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
|
|
689
|
+
|
|
690
|
+
return [
|
|
691
|
+
{
|
|
692
|
+
connKey: "shadow",
|
|
693
|
+
batchNo,
|
|
694
|
+
applied,
|
|
695
|
+
},
|
|
696
|
+
];
|
|
697
|
+
} catch (e) {
|
|
698
|
+
console.error(e);
|
|
699
|
+
throw new ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
|
|
700
|
+
} finally {
|
|
701
|
+
await tdb.destroy();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* 모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제합니다.
|
|
707
|
+
*
|
|
708
|
+
* CLI에서 사용됩니다.
|
|
709
|
+
*
|
|
710
|
+
* @returns
|
|
711
|
+
*/
|
|
712
|
+
async resetAll() {
|
|
713
|
+
const answer = await prompts({
|
|
714
|
+
type: "confirm",
|
|
715
|
+
name: "value",
|
|
716
|
+
message: "모든 DB를 롤백하고 전체 마이그레이션 파일을 삭제하시겠습니까?",
|
|
717
|
+
initial: false,
|
|
718
|
+
});
|
|
719
|
+
if (answer.value === false) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
console.time(chalk.red("rollback-all:"));
|
|
724
|
+
const rollbackAllResult = await Promise.all(
|
|
725
|
+
this.targets.apply.map(async (db) => {
|
|
726
|
+
await db.migrate.forceFreeMigrationsLock();
|
|
727
|
+
return db.migrate.rollback(undefined, true);
|
|
728
|
+
})
|
|
729
|
+
);
|
|
730
|
+
console.log({ rollbackAllResult });
|
|
731
|
+
console.timeEnd(chalk.red("rollback-all:"));
|
|
732
|
+
|
|
733
|
+
const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
734
|
+
console.time(chalk.red("delete migration files"));
|
|
735
|
+
execSync(`rm -f ${migrationsDir}/*`);
|
|
736
|
+
execSync(`rm -f ${migrationsDir.replace("/src/", "/dist/")}/*`);
|
|
737
|
+
console.timeEnd(chalk.red("delete migration files"));
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private async compareMigrations(
|
|
741
|
+
compareDB: Knex
|
|
742
|
+
): Promise<GenMigrationCode[]> {
|
|
743
|
+
// Entity 순회하여 싱크
|
|
744
|
+
const entityIds = EntityManager.getAllIds();
|
|
745
|
+
|
|
746
|
+
// 조인테이블 포함하여 Entity에서 MigrationSet 추출
|
|
747
|
+
const entitySetsWithJoinTable = entityIds
|
|
748
|
+
.filter((entityId) => EntityManager.get(entityId).props.length > 0)
|
|
749
|
+
.map((entityId) =>
|
|
750
|
+
getMigrationSetFromEntity(EntityManager.get(entityId))
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
// 조인테이블만 추출
|
|
754
|
+
const joinTablesWithDup = entitySetsWithJoinTable
|
|
755
|
+
.map((entitySet) => entitySet.joinTables)
|
|
756
|
+
.flat();
|
|
757
|
+
// 중복 제거 (중복인 경우 indexes를 병합)
|
|
758
|
+
const joinTables = Object.values(
|
|
759
|
+
_.groupBy(joinTablesWithDup, (jt) => jt.table)
|
|
760
|
+
).map((tables) => {
|
|
761
|
+
if (tables.length === 1) {
|
|
762
|
+
return tables[0];
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
...tables[0],
|
|
766
|
+
indexes: _.uniqBy(
|
|
767
|
+
tables.flatMap((t) => t.indexes),
|
|
768
|
+
(index) => [index.type, ...index.columns.sort()].join("-")
|
|
769
|
+
),
|
|
770
|
+
};
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// 조인테이블 포함하여 MigrationSet 배열
|
|
774
|
+
const entitySets: MigrationSet[] = [
|
|
775
|
+
...entitySetsWithJoinTable,
|
|
776
|
+
...joinTables,
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
const codes: GenMigrationCode[] = (
|
|
780
|
+
await Promise.all(
|
|
781
|
+
entitySets.map(async (entitySet) => {
|
|
782
|
+
const dbSet = await getMigrationSetFromDB(compareDB, entitySet.table);
|
|
783
|
+
|
|
784
|
+
if (dbSet === null) {
|
|
785
|
+
// 기존 테이블 없음, 새로 테이블 생성
|
|
786
|
+
return await generateCreateCode(entitySet);
|
|
787
|
+
} else {
|
|
788
|
+
// 기존 테이블 존재하는 케이스
|
|
789
|
+
return await generateAlterCode(entitySet, dbSet);
|
|
790
|
+
}
|
|
791
|
+
})
|
|
792
|
+
)
|
|
793
|
+
).flat();
|
|
794
|
+
|
|
795
|
+
// normal 타입이 앞으로, foreign이 뒤로
|
|
796
|
+
codes.sort((codeA, codeB) => {
|
|
797
|
+
if (codeA.type === "foreign" && codeB.type == "normal") {
|
|
798
|
+
return 1;
|
|
799
|
+
} else if (codeA.type === "normal" && codeB.type === "foreign") {
|
|
800
|
+
return -1;
|
|
801
|
+
} else {
|
|
802
|
+
return 0;
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
return codes;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 마이그레이션 대상 커넥션을 종료합니다.
|
|
811
|
+
*
|
|
812
|
+
* CLI에서 사용됩니다.
|
|
813
|
+
*
|
|
814
|
+
* @returns {Promise<void>} 종료 결과
|
|
815
|
+
*/
|
|
816
|
+
async destroy(): Promise<void> {
|
|
817
|
+
await Promise.all(
|
|
818
|
+
this.targets.apply.map((db) => {
|
|
819
|
+
return db.destroy();
|
|
820
|
+
})
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
}
|