sonamu 0.5.7 → 0.6.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/dist/api/base-frame.js +12 -2
- package/dist/api/caster.js +66 -2
- package/dist/api/code-converters.js +489 -2
- package/dist/api/config.d.ts +76 -0
- package/dist/api/config.d.ts.map +1 -0
- package/dist/api/config.js +32 -0
- package/dist/api/context.d.ts +1 -0
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +3 -2
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +142 -2
- package/dist/api/index.js +9 -2
- package/dist/api/sonamu.d.ts +8 -22
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +482 -2
- package/dist/bin/build-config.d.ts +2 -1
- package/dist/bin/build-config.d.ts.map +1 -1
- package/dist/bin/build-config.js +12 -2
- package/dist/bin/cli-wrapper.js +71 -2
- package/dist/bin/cli.js +418 -2
- package/dist/bin/hot-hook-register.d.ts +11 -0
- package/dist/bin/hot-hook-register.d.ts.map +1 -0
- package/dist/bin/hot-hook-register.js +21 -0
- package/dist/database/_batch_update.js +78 -2
- package/dist/database/base-model.js +247 -2
- package/dist/database/code-generator.js +53 -2
- package/dist/database/db.d.ts +2 -16
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +132 -2
- package/dist/database/knex-plugins/knex-on-duplicate-update.js +39 -2
- package/dist/database/puri-wrapper.js +109 -2
- package/dist/database/puri.d.ts +23 -16
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +539 -2
- package/dist/database/puri.types.d.ts +8 -3
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +3 -2
- package/dist/database/transaction-context.js +14 -2
- package/dist/database/upsert-builder.js +215 -2
- package/dist/entity/entity-manager.d.ts +3 -1
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +114 -2
- package/dist/entity/entity-utils.js +210 -2
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +651 -2
- package/dist/exceptions/error-handler.js +29 -2
- package/dist/exceptions/so-exceptions.js +85 -2
- package/dist/file-storage/driver.js +79 -2
- package/dist/file-storage/file-storage.js +75 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/migration/code-generation.js +558 -2
- package/dist/migration/migration-set.js +364 -2
- package/dist/migration/migrator.d.ts +0 -9
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +510 -2
- package/dist/migration/types.js +3 -2
- package/dist/naite/naite.d.ts +12 -0
- package/dist/naite/naite.d.ts.map +1 -0
- package/dist/naite/naite.js +72 -0
- package/dist/stream/index.js +3 -2
- package/dist/stream/sse.js +38 -2
- package/dist/syncer/api-parser.d.ts +20 -0
- package/dist/syncer/api-parser.d.ts.map +1 -0
- package/dist/syncer/api-parser.js +229 -0
- package/dist/syncer/checksum.d.ts +21 -0
- package/dist/syncer/checksum.d.ts.map +1 -0
- package/dist/syncer/checksum.js +98 -0
- package/dist/syncer/code-generator.d.ts +20 -0
- package/dist/syncer/code-generator.d.ts.map +1 -0
- package/dist/syncer/code-generator.js +141 -0
- package/dist/syncer/entity-operations.d.ts +17 -0
- package/dist/syncer/entity-operations.d.ts.map +1 -0
- package/dist/syncer/entity-operations.js +58 -0
- package/dist/syncer/file-patterns.d.ts +29 -0
- package/dist/syncer/file-patterns.d.ts.map +1 -0
- package/dist/syncer/file-patterns.js +38 -0
- package/dist/syncer/index.d.ts +6 -0
- package/dist/syncer/index.d.ts.map +1 -1
- package/dist/syncer/index.js +9 -2
- package/dist/syncer/module-loader.d.ts +35 -0
- package/dist/syncer/module-loader.d.ts.map +1 -0
- package/dist/syncer/module-loader.js +82 -0
- package/dist/syncer/syncer.d.ts +93 -108
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +375 -2
- package/dist/template/entity-converter.d.ts +14 -0
- package/dist/template/entity-converter.d.ts.map +1 -0
- package/dist/template/entity-converter.js +101 -0
- package/dist/template/helpers.d.ts +23 -0
- package/dist/template/helpers.d.ts.map +1 -0
- package/dist/template/helpers.js +64 -0
- package/dist/{templates → template/implementations}/entity.template.d.ts +3 -3
- package/dist/template/implementations/entity.template.d.ts.map +1 -0
- package/dist/template/implementations/entity.template.js +87 -0
- package/dist/{templates → template/implementations}/generated.template.d.ts +3 -3
- package/dist/template/implementations/generated.template.d.ts.map +1 -0
- package/dist/template/implementations/generated.template.js +232 -0
- package/dist/{templates → template/implementations}/generated_http.template.d.ts +3 -3
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -0
- package/dist/template/implementations/generated_http.template.js +131 -0
- package/dist/{templates → template/implementations}/generated_sso.template.d.ts +3 -3
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -0
- package/dist/template/implementations/generated_sso.template.js +105 -0
- package/dist/{templates → template/implementations}/init_types.template.d.ts +3 -3
- package/dist/template/implementations/init_types.template.d.ts.map +1 -0
- package/dist/template/implementations/init_types.template.js +38 -0
- package/dist/template/implementations/model.template.d.ts +17 -0
- package/dist/template/implementations/model.template.d.ts.map +1 -0
- package/dist/template/implementations/model.template.js +171 -0
- package/dist/{templates → template/implementations}/model_test.template.d.ts +3 -3
- package/dist/template/implementations/model_test.template.d.ts.map +1 -0
- package/dist/template/implementations/model_test.template.js +35 -0
- package/dist/{templates → template/implementations}/service.template.d.ts +6 -6
- package/dist/template/implementations/service.template.d.ts.map +1 -0
- package/dist/template/implementations/service.template.js +193 -0
- package/dist/{templates → template/implementations}/view_enums_buttonset.template.d.ts +3 -3
- package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +1 -0
- package/dist/template/implementations/view_enums_buttonset.template.js +31 -0
- package/dist/{templates → template/implementations}/view_enums_dropdown.template.d.ts +3 -4
- package/dist/template/implementations/view_enums_dropdown.template.d.ts.map +1 -0
- package/dist/template/implementations/view_enums_dropdown.template.js +50 -0
- package/dist/{templates → template/implementations}/view_enums_select.template.d.ts +3 -3
- package/dist/template/implementations/view_enums_select.template.d.ts.map +1 -0
- package/dist/template/implementations/view_enums_select.template.js +55 -0
- package/dist/{templates → template/implementations}/view_form.template.d.ts +5 -5
- package/dist/template/implementations/view_form.template.d.ts.map +1 -0
- package/dist/template/implementations/view_form.template.js +337 -0
- package/dist/{templates → template/implementations}/view_id_all_select.template.d.ts +3 -3
- package/dist/template/implementations/view_id_all_select.template.d.ts.map +1 -0
- package/dist/template/implementations/view_id_all_select.template.js +31 -0
- package/dist/{templates → template/implementations}/view_id_async_select.template.d.ts +3 -3
- package/dist/template/implementations/view_id_async_select.template.d.ts.map +1 -0
- package/dist/template/implementations/view_id_async_select.template.js +105 -0
- package/dist/{templates → template/implementations}/view_list.template.d.ts +5 -13
- package/dist/template/implementations/view_list.template.d.ts.map +1 -0
- package/dist/template/implementations/view_list.template.js +465 -0
- package/dist/{templates → template/implementations}/view_list_columns.template.d.ts +3 -3
- package/dist/template/implementations/view_list_columns.template.d.ts.map +1 -0
- package/dist/template/implementations/view_list_columns.template.js +49 -0
- package/dist/{templates → template/implementations}/view_search_input.template.d.ts +3 -3
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -0
- package/dist/template/implementations/view_search_input.template.js +64 -0
- package/dist/template/index.d.ts +5 -0
- package/dist/template/index.d.ts.map +1 -0
- package/dist/template/index.js +6 -0
- package/dist/template/template.d.ts +39 -0
- package/dist/template/template.d.ts.map +1 -0
- package/dist/template/template.js +47 -0
- package/dist/template/zod-converter.d.ts +18 -0
- package/dist/template/zod-converter.d.ts.map +1 -0
- package/dist/template/zod-converter.js +166 -0
- package/dist/testing/_relation-graph.js +80 -2
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +521 -2
- package/dist/types/types.d.ts +39 -40
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +289 -2
- package/dist/typings/knex.d.js +3 -2
- package/dist/utils/async-utils.d.ts +7 -0
- package/dist/utils/async-utils.d.ts.map +1 -1
- package/dist/utils/async-utils.js +57 -2
- package/dist/utils/console-util.d.ts +2 -0
- package/dist/utils/console-util.d.ts.map +1 -0
- package/dist/utils/console-util.js +6 -0
- package/dist/utils/controller.js +26 -2
- package/dist/utils/esm-utils.d.ts +45 -0
- package/dist/utils/esm-utils.d.ts.map +1 -0
- package/dist/utils/esm-utils.js +56 -0
- package/dist/utils/fs-utils.js +17 -2
- package/dist/utils/lodash-able.js +6 -2
- package/dist/utils/model.js +22 -2
- package/dist/utils/path-utils.d.ts +89 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +60 -0
- package/dist/utils/process-utils.d.ts +13 -0
- package/dist/utils/process-utils.d.ts.map +1 -0
- package/dist/utils/process-utils.js +36 -0
- package/dist/utils/sql-parser.js +35 -2
- package/dist/utils/utils.d.ts +4 -7
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +33 -2
- package/dist/utils/zod-error.d.ts.map +1 -1
- package/dist/utils/zod-error.js +19 -2
- package/package.json +21 -8
- package/src/api/code-converters.ts +2 -2
- package/src/api/config.ts +142 -0
- package/src/api/context.ts +1 -0
- package/src/api/decorators.ts +1 -0
- package/src/api/sonamu.ts +81 -67
- package/src/bin/build-config.ts +2 -1
- package/src/bin/cli-wrapper.ts +10 -3
- package/src/bin/cli.ts +108 -56
- package/src/bin/hot-hook-register.ts +22 -0
- package/src/database/base-model.ts +1 -1
- package/src/database/code-generator.ts +1 -1
- package/src/database/db.ts +10 -52
- package/src/database/puri.ts +78 -53
- package/src/database/puri.types.ts +18 -5
- package/src/database/upsert-builder.ts +1 -1
- package/src/entity/entity-manager.ts +19 -15
- package/src/entity/entity.ts +4 -3
- package/src/index.ts +2 -0
- package/src/migration/code-generation.ts +1 -1
- package/src/migration/migration-set.ts +1 -1
- package/src/migration/migrator.ts +23 -152
- package/src/naite/naite.ts +70 -0
- package/src/syncer/api-parser.ts +299 -0
- package/src/syncer/checksum.ts +152 -0
- package/src/syncer/code-generator.ts +202 -0
- package/src/syncer/entity-operations.ts +68 -0
- package/src/syncer/file-patterns.ts +56 -0
- package/src/syncer/index.ts +6 -0
- package/src/syncer/module-loader.ts +125 -0
- package/src/syncer/syncer.ts +363 -1420
- package/src/template/entity-converter.ts +123 -0
- package/src/template/helpers.ts +84 -0
- package/src/{templates → template/implementations}/entity.template.ts +4 -4
- package/src/{templates → template/implementations}/generated.template.ts +9 -9
- package/src/{templates → template/implementations}/generated_http.template.ts +9 -6
- package/src/{templates → template/implementations}/generated_sso.template.ts +7 -7
- package/src/{templates → template/implementations}/init_types.template.ts +4 -4
- package/src/{templates → template/implementations}/model.template.ts +9 -9
- package/src/{templates → template/implementations}/model_test.template.ts +5 -5
- package/src/{templates → template/implementations}/service.template.ts +19 -11
- package/src/{templates → template/implementations}/view_enums_buttonset.template.ts +3 -3
- package/src/{templates → template/implementations}/view_enums_dropdown.template.ts +5 -21
- package/src/{templates → template/implementations}/view_enums_select.template.ts +4 -4
- package/src/{templates → template/implementations}/view_form.template.ts +11 -13
- package/src/{templates → template/implementations}/view_id_all_select.template.ts +3 -3
- package/src/{templates → template/implementations}/view_id_async_select.template.ts +3 -3
- package/src/{templates → template/implementations}/view_list.template.ts +13 -64
- package/src/{templates → template/implementations}/view_list_columns.template.ts +3 -3
- package/src/{templates → template/implementations}/view_search_input.template.ts +3 -3
- package/src/template/index.ts +4 -0
- package/src/template/template.ts +86 -0
- package/src/template/zod-converter.ts +219 -0
- package/src/testing/fixture-manager.ts +8 -1
- package/src/types/types.ts +38 -61
- package/src/utils/async-utils.ts +17 -0
- package/src/utils/console-util.ts +4 -0
- package/src/utils/esm-utils.ts +69 -0
- package/src/utils/path-utils.ts +102 -0
- package/src/utils/process-utils.ts +46 -0
- package/src/utils/sql-parser.ts +1 -1
- package/src/utils/utils.ts +14 -40
- package/src/utils/zod-error.ts +0 -1
- package/dist/api/base-frame.js.map +0 -1
- package/dist/api/caster.js.map +0 -1
- package/dist/api/code-converters.js.map +0 -1
- package/dist/api/context.js.map +0 -1
- package/dist/api/decorators.js.map +0 -1
- package/dist/api/index.js.map +0 -1
- package/dist/api/sonamu.js.map +0 -1
- package/dist/bin/build-config.js.map +0 -1
- package/dist/bin/cli-wrapper.js.map +0 -1
- package/dist/bin/cli.js.map +0 -1
- package/dist/database/_batch_update.js.map +0 -1
- package/dist/database/base-model.js.map +0 -1
- package/dist/database/code-generator.js.map +0 -1
- package/dist/database/db.js.map +0 -1
- package/dist/database/knex-plugins/knex-on-duplicate-update.js.map +0 -1
- package/dist/database/puri-wrapper.js.map +0 -1
- package/dist/database/puri.js.map +0 -1
- package/dist/database/puri.types.js.map +0 -1
- package/dist/database/transaction-context.js.map +0 -1
- package/dist/database/upsert-builder.js.map +0 -1
- package/dist/entity/entity-manager.js.map +0 -1
- package/dist/entity/entity-utils.js.map +0 -1
- package/dist/entity/entity.js.map +0 -1
- package/dist/exceptions/error-handler.js.map +0 -1
- package/dist/exceptions/so-exceptions.js.map +0 -1
- package/dist/file-storage/driver.js.map +0 -1
- package/dist/file-storage/file-storage.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/migration/code-generation.js.map +0 -1
- package/dist/migration/migration-set.js.map +0 -1
- package/dist/migration/migrator.js.map +0 -1
- package/dist/migration/types.js.map +0 -1
- package/dist/stream/index.js.map +0 -1
- package/dist/stream/sse.js.map +0 -1
- package/dist/syncer/index.js.map +0 -1
- package/dist/syncer/syncer.js.map +0 -1
- package/dist/templates/base-template.d.ts +0 -13
- package/dist/templates/base-template.d.ts.map +0 -1
- package/dist/templates/base-template.js +0 -2
- package/dist/templates/base-template.js.map +0 -1
- package/dist/templates/entity.template.d.ts.map +0 -1
- package/dist/templates/entity.template.js +0 -2
- package/dist/templates/entity.template.js.map +0 -1
- package/dist/templates/generated.template.d.ts.map +0 -1
- package/dist/templates/generated.template.js +0 -2
- package/dist/templates/generated.template.js.map +0 -1
- package/dist/templates/generated_http.template.d.ts.map +0 -1
- package/dist/templates/generated_http.template.js +0 -2
- package/dist/templates/generated_http.template.js.map +0 -1
- package/dist/templates/generated_sso.template.d.ts.map +0 -1
- package/dist/templates/generated_sso.template.js +0 -2
- package/dist/templates/generated_sso.template.js.map +0 -1
- package/dist/templates/index.d.ts +0 -2
- package/dist/templates/index.d.ts.map +0 -1
- package/dist/templates/index.js +0 -2
- package/dist/templates/index.js.map +0 -1
- package/dist/templates/init_types.template.d.ts.map +0 -1
- package/dist/templates/init_types.template.js +0 -2
- package/dist/templates/init_types.template.js.map +0 -1
- package/dist/templates/model.template.d.ts +0 -17
- package/dist/templates/model.template.d.ts.map +0 -1
- package/dist/templates/model.template.js +0 -2
- package/dist/templates/model.template.js.map +0 -1
- package/dist/templates/model_test.template.d.ts.map +0 -1
- package/dist/templates/model_test.template.js +0 -2
- package/dist/templates/model_test.template.js.map +0 -1
- package/dist/templates/service.template.d.ts.map +0 -1
- package/dist/templates/service.template.js +0 -2
- package/dist/templates/service.template.js.map +0 -1
- package/dist/templates/view_enums_buttonset.template.d.ts.map +0 -1
- package/dist/templates/view_enums_buttonset.template.js +0 -2
- package/dist/templates/view_enums_buttonset.template.js.map +0 -1
- package/dist/templates/view_enums_dropdown.template.d.ts.map +0 -1
- package/dist/templates/view_enums_dropdown.template.js +0 -2
- package/dist/templates/view_enums_dropdown.template.js.map +0 -1
- package/dist/templates/view_enums_select.template.d.ts.map +0 -1
- package/dist/templates/view_enums_select.template.js +0 -2
- package/dist/templates/view_enums_select.template.js.map +0 -1
- package/dist/templates/view_form.template.d.ts.map +0 -1
- package/dist/templates/view_form.template.js +0 -2
- package/dist/templates/view_form.template.js.map +0 -1
- package/dist/templates/view_id_all_select.template.d.ts.map +0 -1
- package/dist/templates/view_id_all_select.template.js +0 -2
- package/dist/templates/view_id_all_select.template.js.map +0 -1
- package/dist/templates/view_id_async_select.template.d.ts.map +0 -1
- package/dist/templates/view_id_async_select.template.js +0 -2
- package/dist/templates/view_id_async_select.template.js.map +0 -1
- package/dist/templates/view_list.template.d.ts.map +0 -1
- package/dist/templates/view_list.template.js +0 -2
- package/dist/templates/view_list.template.js.map +0 -1
- package/dist/templates/view_list_columns.template.d.ts.map +0 -1
- package/dist/templates/view_list_columns.template.js +0 -2
- package/dist/templates/view_list_columns.template.js.map +0 -1
- package/dist/templates/view_search_input.template.d.ts.map +0 -1
- package/dist/templates/view_search_input.template.js +0 -2
- package/dist/templates/view_search_input.template.js.map +0 -1
- package/dist/testing/_relation-graph.js.map +0 -1
- package/dist/testing/fixture-manager.js.map +0 -1
- package/dist/types/types.js.map +0 -1
- package/dist/typings/knex.d.js.map +0 -1
- package/dist/utils/async-utils.js.map +0 -1
- package/dist/utils/controller.js.map +0 -1
- package/dist/utils/fs-utils.js.map +0 -1
- package/dist/utils/lodash-able.js.map +0 -1
- package/dist/utils/model.js.map +0 -1
- package/dist/utils/sql-parser.js.map +0 -1
- package/dist/utils/utils.js.map +0 -1
- package/dist/utils/zod-error.js.map +0 -1
- package/src/templates/base-template.ts +0 -19
- package/src/templates/index.ts +0 -1
package/src/syncer/syncer.ts
CHANGED
|
@@ -1,300 +1,188 @@
|
|
|
1
1
|
import path, { dirname } from "path";
|
|
2
|
-
import {
|
|
3
|
-
import { createReadStream } from "fs";
|
|
4
|
-
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5
3
|
import { exists } from "../utils/fs-utils";
|
|
6
|
-
import
|
|
7
|
-
import equal from "fast-deep-equal";
|
|
8
|
-
import _, { chunk } from "lodash";
|
|
9
|
-
import inflection from "inflection";
|
|
4
|
+
import * as _ from "lodash-es";
|
|
10
5
|
import { EntityManager, EntityNamesRecord } from "../entity/entity-manager";
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
ApiParam,
|
|
14
|
-
ApiParamType,
|
|
15
|
-
EntityProp,
|
|
16
|
-
EntityPropNode,
|
|
17
|
-
isBelongsToOneRelationProp,
|
|
18
|
-
isBigIntegerProp,
|
|
19
|
-
isBooleanProp,
|
|
20
|
-
isDateProp,
|
|
21
|
-
isDateTimeProp,
|
|
22
|
-
isDecimalProp,
|
|
23
|
-
isDoubleProp,
|
|
24
|
-
isEnumProp,
|
|
25
|
-
isFloatProp,
|
|
26
|
-
isIntegerProp,
|
|
27
|
-
isJsonProp,
|
|
28
|
-
isOneToOneRelationProp,
|
|
29
|
-
isRelationProp,
|
|
30
|
-
isStringProp,
|
|
31
|
-
isTextProp,
|
|
32
|
-
isTimeProp,
|
|
33
|
-
isTimestampProp,
|
|
34
|
-
isUuidProp,
|
|
35
|
-
isVirtualProp,
|
|
36
|
-
} from "../types/types";
|
|
37
|
-
import {
|
|
38
|
-
ApiDecoratorOptions,
|
|
39
|
-
registeredApis,
|
|
40
|
-
ExtendedApi,
|
|
41
|
-
} from "../api/decorators";
|
|
42
|
-
import { z } from "zod";
|
|
6
|
+
import { GenerateOptions } from "../types/types";
|
|
43
7
|
import chalk from "chalk";
|
|
44
|
-
import {
|
|
45
|
-
TemplateKey,
|
|
46
|
-
PathAndCode,
|
|
47
|
-
TemplateOptions,
|
|
48
|
-
GenerateOptions,
|
|
49
|
-
RenderingNode,
|
|
50
|
-
} from "../types/types";
|
|
51
|
-
import {
|
|
52
|
-
AlreadyProcessedException,
|
|
53
|
-
BadRequestException,
|
|
54
|
-
ServiceUnavailableException,
|
|
55
|
-
} from "../exceptions/so-exceptions";
|
|
56
|
-
import { wrapIf } from "../utils/lodash-able";
|
|
57
|
-
import { getTextTypeLength } from "../api/code-converters";
|
|
58
|
-
import { Template } from "../templates/base-template";
|
|
59
|
-
import { Template__generated } from "../templates/generated.template";
|
|
60
|
-
import { Template__init_types } from "../templates/init_types.template";
|
|
61
|
-
import { Template__entity } from "../templates/entity.template";
|
|
62
|
-
import { Template__model } from "../templates/model.template";
|
|
63
|
-
import { Template__model_test } from "../templates/model_test.template";
|
|
64
|
-
import { Template__service } from "../templates/service.template";
|
|
65
|
-
import { Template__view_form } from "../templates/view_form.template";
|
|
66
|
-
import { Template__view_list } from "../templates/view_list.template";
|
|
67
|
-
import prettier from "prettier";
|
|
68
|
-
import { Template__view_id_all_select } from "../templates/view_id_all_select.template";
|
|
69
|
-
import { Template__view_id_async_select } from "../templates/view_id_async_select.template";
|
|
70
|
-
import { Template__view_enums_dropdown } from "../templates/view_enums_dropdown.template";
|
|
71
|
-
import { Template__view_enums_select } from "../templates/view_enums_select.template";
|
|
72
|
-
import { Template__view_enums_buttonset } from "../templates/view_enums_buttonset.template";
|
|
73
|
-
import { Template__view_search_input } from "../templates/view_search_input.template";
|
|
74
|
-
import { Template__view_list_columns } from "../templates/view_list_columns.template";
|
|
75
|
-
import { Template__generated_http } from "../templates/generated_http.template";
|
|
8
|
+
import { TemplateKey, TemplateOptions } from "../types/types";
|
|
76
9
|
import { Sonamu } from "../api/sonamu";
|
|
77
|
-
import { Template__generated_sso } from "../templates/generated_sso.template";
|
|
78
|
-
import { setTimeout as setTimeoutPromises } from "timers/promises";
|
|
79
10
|
import assert from "assert";
|
|
80
|
-
import * as swc from "@swc/core";
|
|
81
11
|
import { minimatch } from "minimatch";
|
|
12
|
+
import { mapAsync, reduceAsync } from "../utils/async-utils";
|
|
13
|
+
import { centerText } from "../utils/console-util";
|
|
14
|
+
import { runWithGracefulShutdown } from "../utils/process-utils";
|
|
15
|
+
import { AbsolutePath } from "../utils/path-utils";
|
|
16
|
+
import { generateTemplate, renderTemplate } from "./code-generator";
|
|
17
|
+
import { Template } from "../template";
|
|
82
18
|
import {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
19
|
+
FileType,
|
|
20
|
+
getChecksumPatternGroupInAbsolutePath,
|
|
21
|
+
} from "./file-patterns";
|
|
22
|
+
import {
|
|
23
|
+
findChangedFilesUsingChecksums,
|
|
24
|
+
renewChecksums,
|
|
25
|
+
areFilesSame,
|
|
26
|
+
} from "./checksum";
|
|
27
|
+
import {
|
|
28
|
+
loadApis,
|
|
29
|
+
loadModels,
|
|
30
|
+
loadTypes,
|
|
31
|
+
LoadedApis,
|
|
32
|
+
LoadedModels,
|
|
33
|
+
LoadedTypes,
|
|
34
|
+
} from "./module-loader";
|
|
35
|
+
import { createEntity, delEntity } from "./entity-operations";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
import { hot } from "@sonamu-kit/hot-hook";
|
|
38
|
+
|
|
103
39
|
type DiffGroups = {
|
|
104
|
-
[key in FileType]:
|
|
105
|
-
};
|
|
106
|
-
export type RenderedTemplate = {
|
|
107
|
-
target: string;
|
|
108
|
-
path: string;
|
|
109
|
-
body: string;
|
|
110
|
-
importKeys: string[];
|
|
111
|
-
customHeaders?: string[];
|
|
112
|
-
preTemplates?: {
|
|
113
|
-
key: TemplateKey;
|
|
114
|
-
options: TemplateOptions[TemplateKey];
|
|
115
|
-
}[];
|
|
40
|
+
[key in FileType]: AbsolutePath[];
|
|
116
41
|
};
|
|
117
42
|
|
|
118
43
|
export class Syncer {
|
|
119
|
-
apis:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
returnType: ApiParamType;
|
|
123
|
-
modelName: string;
|
|
124
|
-
methodName: string;
|
|
125
|
-
path: string;
|
|
126
|
-
options: ApiDecoratorOptions;
|
|
127
|
-
}[] = [];
|
|
128
|
-
types: { [typeName: string]: z.ZodObject<any> } = {};
|
|
129
|
-
models: { [modelName: string]: unknown } = {};
|
|
44
|
+
apis: LoadedApis = [];
|
|
45
|
+
types: LoadedTypes = {};
|
|
46
|
+
models: LoadedModels = {};
|
|
130
47
|
isSyncing: boolean = false;
|
|
131
48
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
functions: Sonamu.apiRootPath + "/src/application/**/*.functions.ts",
|
|
138
|
-
/* compiled-JS 체크 */
|
|
139
|
-
model: Sonamu.apiRootPath + "/dist/application/**/*.model.js",
|
|
140
|
-
frame: Sonamu.apiRootPath + "/dist/application/**/*.frame.js",
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
get checksumsPath(): string {
|
|
144
|
-
return path.join(Sonamu.apiRootPath, "/sonamu.lock");
|
|
145
|
-
}
|
|
146
|
-
public constructor() {}
|
|
147
|
-
|
|
49
|
+
/**
|
|
50
|
+
* 체크섬이 변경된 부분에 대해 싱크를 진행합니다.
|
|
51
|
+
* 다만 sonamu.shared.ts는 체크섬 비교 없이 무조건 싱크(복사)합니다.
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
148
54
|
async sync(): Promise<void> {
|
|
149
55
|
const { targets } = Sonamu.config.sync;
|
|
150
56
|
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
? __dirname
|
|
154
|
-
: path.join(__dirname, "./syncer");
|
|
155
|
-
|
|
156
|
-
// 트리거와 무관하게 shared 분배
|
|
157
|
-
await Promise.all(
|
|
158
|
-
targets.map(async (target) => {
|
|
159
|
-
const srcCodePath = path
|
|
160
|
-
.join(currentDirname, `../shared/${target}.shared.ts.txt`)
|
|
161
|
-
.replace("/dist/", "/src/");
|
|
162
|
-
if (!(await exists(srcCodePath))) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
57
|
+
// sonamu.shared.ts는 무조건 싱크(복사)합니다.
|
|
58
|
+
await this.copySharedToTargets(targets);
|
|
165
59
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
60
|
+
// 그 다음부터는 변경된 파일을 찾아서 동기화 작업을 실행합니다.
|
|
61
|
+
const changedFiles = await findChangedFilesUsingChecksums();
|
|
62
|
+
if (changedFiles.length === 0) {
|
|
63
|
+
console.log(chalk.black.bgGreen(centerText("All files are synced!")));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
171
66
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})();
|
|
67
|
+
// 만약 싱크 중에 프로세스가 죽으면 꼬여버리기 때문에,
|
|
68
|
+
// 시그널에도 잠시 버틸 수 있는 환경 속에서 싱크를 실행합니다.
|
|
69
|
+
await runWithGracefulShutdown(
|
|
70
|
+
async () => {
|
|
71
|
+
// 얘가 싱크 작업 수행하는 본체입니다.
|
|
72
|
+
await this.doSyncActions(changedFiles);
|
|
179
73
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
console.log(chalk.blue("shared.ts is synced"));
|
|
185
|
-
})
|
|
74
|
+
// 싱크 액션이 끝나면 항상 체크섬을 다시 갱신합니다.
|
|
75
|
+
await renewChecksums();
|
|
76
|
+
},
|
|
77
|
+
{ whenThisHappens: "SIGUSR2", waitForUpTo: 20000 }
|
|
186
78
|
);
|
|
79
|
+
}
|
|
187
80
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Watcher가 감지한 파일 변경 사항에 대해 싱크를 진행합니다.
|
|
83
|
+
* 주어진 변경 파일들 중 체크섬 관리 대상인 것들만 가져다가 싱크를 진행합니다.
|
|
84
|
+
* 체크섬 파일 업데이트는 여기에서 하지 않습니다. 호출자가 합니다.
|
|
85
|
+
* @param diffFilePath - 변경 파일들. 프로젝트 루트부터 "src/" 또는 "dist/"로 시작하는 상대 경로입니다. 예시: "src/application/user/user.model.ts"
|
|
86
|
+
*/
|
|
87
|
+
async syncFromWatcher(
|
|
88
|
+
event: string,
|
|
89
|
+
diffFilePath: AbsolutePath
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
if (event !== "change" && event !== "add" && event !== "unlink") {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
192
94
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
95
|
+
// 일단 변경된 파일과 dependent 파일들을 invalidate 합니다.
|
|
96
|
+
// 한 번 이상 import된 친구들에 대해서만 실제 작업이 일어납니다.
|
|
97
|
+
// 그러니 안심하고 invalidate 해도 됩니다.
|
|
98
|
+
const invalidatedPaths = await hot.invalidateFile(diffFilePath, event);
|
|
99
|
+
if (invalidatedPaths.length > 0) {
|
|
198
100
|
console.log(
|
|
199
|
-
chalk.
|
|
101
|
+
chalk.bold(
|
|
102
|
+
`🔄 Invalidated:\n${chalk.blue(invalidatedPaths.map((p) => `- ${path.relative(Sonamu.apiRootPath, p)}`).join("\n"))}`
|
|
103
|
+
)
|
|
200
104
|
);
|
|
201
|
-
return;
|
|
202
105
|
}
|
|
203
106
|
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
107
|
+
const isInCheckPatternGroup = Object.values(
|
|
108
|
+
getChecksumPatternGroupInAbsolutePath()
|
|
109
|
+
).some((pattern) => minimatch(diffFilePath, pattern));
|
|
110
|
+
|
|
111
|
+
// 할 일(sync)이 있으면 합니다.
|
|
112
|
+
if (isInCheckPatternGroup) {
|
|
113
|
+
await this.doSyncActions([diffFilePath]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 싱크 작업이 끝나면 모든 모듈을 로드합니다.
|
|
117
|
+
// hot-hook에 의해 invalidate된 부분들이 아니라면 캐시 그대로 유지합니다.
|
|
118
|
+
await this.autoloadTypes();
|
|
119
|
+
await this.autoloadModels();
|
|
120
|
+
await this.autoloadApis();
|
|
121
|
+
|
|
122
|
+
this.syncUI();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async copySharedToTargets(targets: string[]): Promise<void> {
|
|
126
|
+
for (const target of targets) {
|
|
127
|
+
// 지금 가져가려는 이 파일은 Sonamu 코드베이스의 일부입니다.
|
|
128
|
+
// 그런데 dist 속 빌드된 소스 코드 파일이 필요한 것이 아니고, src에만 있는 텍스트 파일이 필요합니다.
|
|
129
|
+
// 따라서 /src/에서 찾습니다.
|
|
130
|
+
const srcPath = path.join(
|
|
131
|
+
import.meta.dirname.replace("/dist/", "/src/"),
|
|
132
|
+
`../shared/${target}.shared.ts.txt`
|
|
133
|
+
);
|
|
134
|
+
if (!(await exists(srcPath))) {
|
|
135
|
+
return;
|
|
209
136
|
}
|
|
210
|
-
console.log(chalk.magentaBright(`wait for syncing done....`));
|
|
211
|
-
|
|
212
|
-
// 싱크 완료 대기
|
|
213
|
-
try {
|
|
214
|
-
await setTimeoutPromises(20000, "waiting-sync", { signal: abc.signal });
|
|
215
|
-
} catch {}
|
|
216
|
-
console.log(chalk.magentaBright(`Syncing DONE!`));
|
|
217
|
-
process.exit(0);
|
|
218
|
-
};
|
|
219
|
-
process.on("SIGUSR2", onSIGUSR2);
|
|
220
137
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const diffFiles = diff.map((r) => r.path);
|
|
228
|
-
console.log("Changed Files: ", diffFiles);
|
|
138
|
+
// 이건 프로젝트에 .ts 소스 코드 파일을 생성하는 것이므로 src의 .ts 경로로 갑니다.
|
|
139
|
+
const destPath = path.join(
|
|
140
|
+
Sonamu.appRootPath,
|
|
141
|
+
target,
|
|
142
|
+
"src/services/sonamu.shared.ts"
|
|
143
|
+
);
|
|
229
144
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
);
|
|
234
|
-
// checksum 오버라이드 (액션 실행 과정 중간에 체크섬이 바뀐 경우)
|
|
235
|
-
currentChecksums = changedChecksums ?? currentChecksums;
|
|
145
|
+
if (await areFilesSame(srcPath, destPath)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
236
148
|
|
|
237
|
-
|
|
238
|
-
await this.saveChecksums(currentChecksums);
|
|
149
|
+
await writeFile(destPath, await readFile(srcPath));
|
|
239
150
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
151
|
+
console.log(
|
|
152
|
+
chalk.bold("Copied: ") +
|
|
153
|
+
chalk.blue(path.relative(Sonamu.appRootPath, destPath))
|
|
154
|
+
);
|
|
155
|
+
}
|
|
244
156
|
}
|
|
245
157
|
|
|
246
|
-
async
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
// 다른 부분 찾아 액션
|
|
254
|
-
const diffGroups = _.groupBy(diffFiles, (r) => {
|
|
255
|
-
const matched = r.match(
|
|
256
|
-
/\.(model|types|functions|entity|generated|frame)\.[tj]s/
|
|
257
|
-
);
|
|
258
|
-
return matched?.[1] ?? "unknown";
|
|
259
|
-
}) as unknown as DiffGroups;
|
|
158
|
+
async autoloadTypes() {
|
|
159
|
+
this.types = await loadTypes();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async autoloadModels() {
|
|
163
|
+
this.models = await loadModels();
|
|
164
|
+
}
|
|
260
165
|
|
|
261
|
-
|
|
166
|
+
async autoloadApis() {
|
|
167
|
+
this.apis = await loadApis();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 실제 싱크를 수행하는 본체입니다.
|
|
172
|
+
* 변경된 파일들을 타입별로 분류하고 각 타입에 맞는 액션을 실행합니다.
|
|
173
|
+
* @param diffFilePaths - 변경된 파일들의 절대 경로 목록
|
|
174
|
+
* @returns diffTypes - 변경된 파일의 타입 목록 (entity, types, model 등)
|
|
175
|
+
*/
|
|
176
|
+
private async doSyncActions(
|
|
177
|
+
diffFilePaths: AbsolutePath[]
|
|
178
|
+
): Promise<{ diffTypes: string[] }> {
|
|
179
|
+
const diffGroups = this.calculateDiffGroups(diffFilePaths);
|
|
262
180
|
const diffTypes = Object.keys(diffGroups);
|
|
263
181
|
|
|
264
182
|
// 트리거: entity, types
|
|
265
183
|
// 액션: 스키마 생성
|
|
266
|
-
if (diffTypes.includes("entity")
|
|
267
|
-
await
|
|
268
|
-
|
|
269
|
-
await this.actionGenerateSchemas();
|
|
270
|
-
|
|
271
|
-
// types 생성(entity 새로 추가된 경우)
|
|
272
|
-
// parentId가 없고, types가 없는 경우에만 생성
|
|
273
|
-
const entityId = this.getEntityIdFromPath([
|
|
274
|
-
...(diffGroups["entity"] ?? []),
|
|
275
|
-
])[0];
|
|
276
|
-
if (entityId) {
|
|
277
|
-
const entity = EntityManager.get(entityId);
|
|
278
|
-
const typeFilePath = path.join(
|
|
279
|
-
Sonamu.apiRootPath,
|
|
280
|
-
`src/application/${entity.names.fs}/${entity.names.fs}.types.ts`
|
|
281
|
-
);
|
|
282
|
-
if (entity.parentId === undefined && !(await exists(typeFilePath))) {
|
|
283
|
-
await this.generateTemplate("init_types", { entityId });
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// generated 싱크까지 동시에 처리 후 체크섬 갱신
|
|
288
|
-
diffGroups["generated"] = _.uniq([
|
|
289
|
-
...(diffGroups["generated"] ?? []),
|
|
290
|
-
"/src/application/sonamu.generated.ts",
|
|
291
|
-
]);
|
|
292
|
-
diffTypes.push("generated");
|
|
293
|
-
|
|
294
|
-
// fullSync인 경우만 실행
|
|
295
|
-
if (currentChecksums) {
|
|
296
|
-
currentChecksums = await this.getCurrentChecksums();
|
|
297
|
-
}
|
|
184
|
+
if (diffTypes.includes("entity")) {
|
|
185
|
+
await this.handleEntityChange(diffGroups, diffTypes);
|
|
298
186
|
}
|
|
299
187
|
|
|
300
188
|
// 트리거: types, enums, generated 변경시
|
|
@@ -304,202 +192,183 @@ export class Syncer {
|
|
|
304
192
|
diffTypes.includes("functions") ||
|
|
305
193
|
diffTypes.includes("generated")
|
|
306
194
|
) {
|
|
307
|
-
|
|
308
|
-
[
|
|
309
|
-
...(diffGroups["types"] ?? []),
|
|
310
|
-
...(diffGroups["functions"] ?? []),
|
|
311
|
-
...(diffGroups["generated"] ?? []),
|
|
312
|
-
].map((p) => p.replace("/dist/", "/src/").replace(".js", ".ts"))
|
|
313
|
-
);
|
|
314
|
-
await this.actionSyncFilesToTargets(tsPaths);
|
|
195
|
+
await this.handleTypesOrFunctionsOrGeneratedChange(diffGroups);
|
|
315
196
|
}
|
|
316
197
|
|
|
317
198
|
// 트리거: model
|
|
318
199
|
if (diffTypes.includes("model") || diffTypes.includes("frame")) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
await this.autoloadModels();
|
|
326
|
-
|
|
327
|
-
// Syncer.apis 초기화
|
|
328
|
-
await this.autoloadApis();
|
|
329
|
-
|
|
330
|
-
const params: { namesRecord: EntityNamesRecord; modelTsPath: string }[] =
|
|
331
|
-
mergedGroup.map((modelPath) => {
|
|
332
|
-
if (modelPath.endsWith(".model.js")) {
|
|
333
|
-
const entityId = this.getEntityIdFromPath([modelPath])[0];
|
|
334
|
-
assert(entityId);
|
|
335
|
-
return {
|
|
336
|
-
namesRecord: EntityManager.getNamesFromId(entityId),
|
|
337
|
-
modelTsPath: path.join(
|
|
338
|
-
Sonamu.apiRootPath,
|
|
339
|
-
modelPath
|
|
340
|
-
.replace("/dist/", "/src/")
|
|
341
|
-
.replace(".model.js", ".model.ts")
|
|
342
|
-
),
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
if (modelPath.endsWith("frame.js")) {
|
|
346
|
-
const [, frameName] = modelPath.match(/.+\/(.+)\.frame.js$/) ?? [];
|
|
347
|
-
assert(frameName);
|
|
348
|
-
return {
|
|
349
|
-
namesRecord: EntityManager.getNamesFromId(frameName),
|
|
350
|
-
modelTsPath: path.join(
|
|
351
|
-
Sonamu.apiRootPath,
|
|
352
|
-
modelPath
|
|
353
|
-
.replace("/dist/", "/src/")
|
|
354
|
-
.replace(".frame.js", ".frame.ts")
|
|
355
|
-
),
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
throw new Error("not reachable");
|
|
359
|
-
});
|
|
360
|
-
await this.actionGenerateServices(params);
|
|
361
|
-
|
|
362
|
-
await this.actionGenerateHttps();
|
|
200
|
+
await this.handleModelOrFrameChange(diffGroups);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 트리거: config
|
|
204
|
+
if (diffTypes.includes("config")) {
|
|
205
|
+
await this.actionSyncConfig();
|
|
363
206
|
}
|
|
364
207
|
|
|
365
208
|
return {
|
|
366
209
|
diffTypes,
|
|
367
|
-
changedChecksums: currentChecksums,
|
|
368
210
|
};
|
|
369
211
|
}
|
|
370
212
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const _transpiledFilePaths = await Promise.all(
|
|
380
|
-
chunk.map(async (diffFile) => {
|
|
381
|
-
const { code, map } = await swc.transformFile(diffFile, {
|
|
382
|
-
module: {
|
|
383
|
-
type: "commonjs",
|
|
384
|
-
},
|
|
385
|
-
jsc: {
|
|
386
|
-
parser: {
|
|
387
|
-
syntax: "typescript",
|
|
388
|
-
decorators: true,
|
|
389
|
-
},
|
|
390
|
-
target: "es5",
|
|
391
|
-
},
|
|
392
|
-
sourceMaps: true,
|
|
393
|
-
});
|
|
213
|
+
private calculateDiffGroups(diffFiles: AbsolutePath[]): DiffGroups {
|
|
214
|
+
return _.groupBy(diffFiles, (r) => {
|
|
215
|
+
const matched = r.match(
|
|
216
|
+
/\.(model|types|functions|entity|generated|frame|config)\.[tj]s/
|
|
217
|
+
);
|
|
218
|
+
return matched?.[1] ?? "unknown";
|
|
219
|
+
}) as unknown as DiffGroups;
|
|
220
|
+
}
|
|
394
221
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
222
|
+
private async handleEntityChange(
|
|
223
|
+
diffGroups: DiffGroups,
|
|
224
|
+
diffTypes: string[]
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
// console.log(
|
|
227
|
+
// chalk.gray(
|
|
228
|
+
// `[Processing] Handling entity changes: ${diffGroups["entity"]?.map((p) => path.relative(Sonamu.apiRootPath, p)).join(", ")}`
|
|
229
|
+
// )
|
|
230
|
+
// );
|
|
231
|
+
|
|
232
|
+
await EntityManager.reload();
|
|
233
|
+
|
|
234
|
+
// types 생성(entity 새로 추가된 경우)
|
|
235
|
+
// parentId가 없고, types가 없는 경우에만 생성
|
|
236
|
+
const entityId = EntityManager.getEntityIdFromPath(
|
|
237
|
+
diffGroups["entity"]?.[0]
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (entityId) {
|
|
241
|
+
const entity = EntityManager.get(entityId);
|
|
242
|
+
// 프로젝트에 생성되어야 하는 .ts 파일의 경로입니다.
|
|
243
|
+
const typeFilePath = path.join(
|
|
244
|
+
Sonamu.apiRootPath,
|
|
245
|
+
`src/application/${entity.names.fs}/${entity.names.fs}.types.ts`
|
|
419
246
|
);
|
|
420
|
-
|
|
247
|
+
if (entity.parentId === undefined && !(await exists(typeFilePath))) {
|
|
248
|
+
await generateTemplate("init_types", { entityId });
|
|
249
|
+
}
|
|
421
250
|
}
|
|
422
251
|
|
|
423
|
-
|
|
424
|
-
function clearModuleAndDependents(filePath: string) {
|
|
425
|
-
const resolved = require.resolve(filePath);
|
|
426
|
-
const toDelete = new Set([resolved]);
|
|
252
|
+
await this.actionGenerateSchemas();
|
|
427
253
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
254
|
+
diffGroups["generated"] = _.uniq([
|
|
255
|
+
...(diffGroups["generated"] ?? []),
|
|
256
|
+
path.join(
|
|
257
|
+
Sonamu.apiRootPath,
|
|
258
|
+
"src/application/sonamu.generated.ts"
|
|
259
|
+
) as AbsolutePath,
|
|
260
|
+
]);
|
|
261
|
+
diffTypes.push("generated");
|
|
262
|
+
}
|
|
435
263
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// );
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
transpiledFilePaths.map((filePath) => {
|
|
448
|
-
clearModuleAndDependents(filePath);
|
|
449
|
-
});
|
|
264
|
+
private async handleTypesOrFunctionsOrGeneratedChange(
|
|
265
|
+
diffGroups: DiffGroups
|
|
266
|
+
): Promise<FileType[]> {
|
|
267
|
+
const tsPaths = _.uniq([
|
|
268
|
+
...(diffGroups["types"] ?? []),
|
|
269
|
+
...(diffGroups["functions"] ?? []),
|
|
270
|
+
...(diffGroups["generated"] ?? []),
|
|
271
|
+
]);
|
|
450
272
|
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
minimatch(filePath, pattern)
|
|
457
|
-
);
|
|
458
|
-
})
|
|
459
|
-
.map((filePath) => "/" + path.relative(Sonamu.apiRootPath, filePath));
|
|
460
|
-
await this.doSyncActions(targetFilePaths);
|
|
273
|
+
// console.log(
|
|
274
|
+
// chalk.gray(
|
|
275
|
+
// `[Processing] Handling types/functions/generated changes: ${tsPaths.map((p) => path.relative(Sonamu.apiRootPath, p)).join(", ")}`
|
|
276
|
+
// )
|
|
277
|
+
// );
|
|
461
278
|
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
279
|
+
await this.actionSyncFilesToTargets(tsPaths);
|
|
280
|
+
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async handleModelOrFrameChange(
|
|
285
|
+
diffGroups: DiffGroups
|
|
286
|
+
): Promise<void> {
|
|
287
|
+
const mergedGroup = [
|
|
288
|
+
...(diffGroups["model"] ?? []),
|
|
289
|
+
...(diffGroups["frame"] ?? []),
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// console.log(
|
|
293
|
+
// chalk.gray(
|
|
294
|
+
// `[Processing] Handling model/frame changes: ${mergedGroup.map((p) => path.relative(Sonamu.apiRootPath, p)).join(", ")}`
|
|
295
|
+
// )
|
|
296
|
+
// );
|
|
297
|
+
|
|
298
|
+
// generated_http.template.ts에서 syncer.types를 씁니다.
|
|
299
|
+
// service.template.ts에서 syncer.apis를 씁니다.
|
|
466
300
|
await this.autoloadModels();
|
|
301
|
+
await this.autoloadTypes();
|
|
467
302
|
await this.autoloadApis();
|
|
468
303
|
|
|
469
|
-
|
|
304
|
+
const params: {
|
|
305
|
+
namesRecord: EntityNamesRecord;
|
|
306
|
+
}[] = mergedGroup.map((modelPath) => {
|
|
307
|
+
if (modelPath.endsWith(".model.ts")) {
|
|
308
|
+
const entityId = EntityManager.getEntityIdFromPath(modelPath);
|
|
309
|
+
assert(entityId);
|
|
310
|
+
return {
|
|
311
|
+
namesRecord: EntityManager.getNamesFromId(entityId),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (modelPath.endsWith("frame.ts")) {
|
|
315
|
+
const [, frameName] = modelPath.match(/.+\/(.+)\.frame.js$/) ?? [];
|
|
316
|
+
assert(frameName);
|
|
317
|
+
return {
|
|
318
|
+
namesRecord: EntityManager.getNamesFromId(frameName),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
throw new Error("not reachable");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await this.actionGenerateServices(params);
|
|
325
|
+
await this.actionGenerateHttps();
|
|
470
326
|
}
|
|
471
327
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
328
|
+
// web/.sonamu.env 에 현재 설정값 저장
|
|
329
|
+
async actionSyncConfig() {
|
|
330
|
+
const { host, port } = Sonamu.config.server.listen ?? {};
|
|
331
|
+
const content = `API_HOST=${host ?? "localhost"}\nAPI_PORT=${port ?? 3000}`;
|
|
332
|
+
|
|
333
|
+
await Promise.all(
|
|
334
|
+
Sonamu.config.sync.targets.map(async (target) => {
|
|
335
|
+
await writeFile(
|
|
336
|
+
path.join(Sonamu.appRootPath, target, ".sonamu.env"),
|
|
337
|
+
content
|
|
338
|
+
);
|
|
478
339
|
})
|
|
479
340
|
);
|
|
480
341
|
}
|
|
481
342
|
|
|
482
|
-
|
|
343
|
+
/**
|
|
344
|
+
* sonamu.generated.ts와 sonamu.generated.sso.ts를 생성합니다.
|
|
345
|
+
* @returns 생성된 파일 경로 배열.
|
|
346
|
+
*/
|
|
347
|
+
private async actionGenerateSchemas(): Promise<AbsolutePath[]> {
|
|
483
348
|
return (
|
|
484
349
|
await Promise.all([
|
|
485
|
-
|
|
486
|
-
|
|
350
|
+
generateTemplate("generated_sso", {}, { overwrite: true }),
|
|
351
|
+
generateTemplate("generated", {}, { overwrite: true }),
|
|
487
352
|
])
|
|
488
353
|
)
|
|
489
354
|
.flat()
|
|
490
355
|
.flat();
|
|
491
356
|
}
|
|
492
357
|
|
|
493
|
-
|
|
358
|
+
/**
|
|
359
|
+
* *.service.ts를 생성합니다.
|
|
360
|
+
* @param paramsArray
|
|
361
|
+
* @returns 생성된 파일 경로 배열.
|
|
362
|
+
*/
|
|
363
|
+
private async actionGenerateServices(
|
|
494
364
|
paramsArray: {
|
|
495
365
|
namesRecord: EntityNamesRecord;
|
|
496
|
-
modelTsPath: string;
|
|
497
366
|
}[]
|
|
498
367
|
): Promise<string[]> {
|
|
499
368
|
return (
|
|
500
369
|
await Promise.all(
|
|
501
370
|
paramsArray.map(async (params) =>
|
|
502
|
-
|
|
371
|
+
generateTemplate("service", params, {
|
|
503
372
|
overwrite: true,
|
|
504
373
|
})
|
|
505
374
|
)
|
|
@@ -509,8 +378,12 @@ export class Syncer {
|
|
|
509
378
|
.flat();
|
|
510
379
|
}
|
|
511
380
|
|
|
512
|
-
|
|
513
|
-
|
|
381
|
+
/**
|
|
382
|
+
* sonamu.generated.http를 생성합니다.
|
|
383
|
+
* @returns 생성된 파일 경로.
|
|
384
|
+
*/
|
|
385
|
+
private async actionGenerateHttps(): Promise<AbsolutePath> {
|
|
386
|
+
const [res] = await generateTemplate(
|
|
514
387
|
"generated_http",
|
|
515
388
|
{},
|
|
516
389
|
{ overwrite: true }
|
|
@@ -519,29 +392,14 @@ export class Syncer {
|
|
|
519
392
|
return res;
|
|
520
393
|
}
|
|
521
394
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const nfc = oldFileContent.replace(
|
|
531
|
-
/from "sonamu"/g,
|
|
532
|
-
`from "src/services/sonamu.shared"`
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
if (toPath.includes("/web/")) {
|
|
536
|
-
return nfc.replace(/from "lodash";/g, `from "lodash-es";`);
|
|
537
|
-
} else {
|
|
538
|
-
return nfc;
|
|
539
|
-
}
|
|
540
|
-
})();
|
|
541
|
-
return writeFile(toPath, newFileContent);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
async actionSyncFilesToTargets(tsPaths: string[]): Promise<string[]> {
|
|
395
|
+
/**
|
|
396
|
+
* *.types.ts, *.functions.ts, *.generated.ts를 타겟 디렉토리에 복사합니다.
|
|
397
|
+
* @param tsPaths
|
|
398
|
+
* @returns 복사된 파일 경로 배열.
|
|
399
|
+
*/
|
|
400
|
+
private async actionSyncFilesToTargets(
|
|
401
|
+
tsPaths: AbsolutePath[]
|
|
402
|
+
): Promise<string[]> {
|
|
545
403
|
const { targets } = Sonamu.config.sync;
|
|
546
404
|
const { dir: apiDir } = Sonamu.config.api;
|
|
547
405
|
|
|
@@ -549,8 +407,7 @@ export class Syncer {
|
|
|
549
407
|
await Promise.all(
|
|
550
408
|
targets.map(async (target) =>
|
|
551
409
|
Promise.all(
|
|
552
|
-
tsPaths.map(async (
|
|
553
|
-
const realSrc = Sonamu.apiRootPath + src;
|
|
410
|
+
tsPaths.map(async (realSrc) => {
|
|
554
411
|
const dst = realSrc
|
|
555
412
|
.replace(`/${apiDir}/`, `/${target}/`)
|
|
556
413
|
.replace("/application/", "/services/");
|
|
@@ -560,9 +417,7 @@ export class Syncer {
|
|
|
560
417
|
}
|
|
561
418
|
console.log(
|
|
562
419
|
chalk.bold("Copied: ") +
|
|
563
|
-
chalk.blue(
|
|
564
|
-
`Copied: ${dst.replace(Sonamu.appRootPath + "/", "")}`
|
|
565
|
-
)
|
|
420
|
+
chalk.blue(dst.replace(Sonamu.appRootPath + "/", ""))
|
|
566
421
|
);
|
|
567
422
|
await this.copyFileWithReplaceCoreToShared(realSrc, dst);
|
|
568
423
|
return dst;
|
|
@@ -573,671 +428,49 @@ export class Syncer {
|
|
|
573
428
|
).flat();
|
|
574
429
|
}
|
|
575
430
|
|
|
576
|
-
async
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
)
|
|
584
|
-
)
|
|
585
|
-
)
|
|
586
|
-
.flat()
|
|
587
|
-
.sort();
|
|
588
|
-
|
|
589
|
-
const fileChecksums: {
|
|
590
|
-
path: string;
|
|
591
|
-
checksum: string;
|
|
592
|
-
}[] = await Promise.all(
|
|
593
|
-
filePaths.map(async (filePath) => {
|
|
594
|
-
return {
|
|
595
|
-
path: filePath.substring(Sonamu.apiRootPath.length),
|
|
596
|
-
checksum: await this.getChecksumOfFile(filePath),
|
|
597
|
-
};
|
|
598
|
-
})
|
|
599
|
-
);
|
|
600
|
-
return fileChecksums;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async getPreviousChecksums(): Promise<PathAndChecksum[]> {
|
|
604
|
-
if (!(await exists(this.checksumsPath))) {
|
|
605
|
-
return [];
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const previousChecksums = JSON.parse(
|
|
609
|
-
(await readFile(this.checksumsPath, "utf-8"))
|
|
610
|
-
) as PathAndChecksum[];
|
|
611
|
-
return previousChecksums;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
async saveChecksums(checksums: PathAndChecksum[]): Promise<void> {
|
|
615
|
-
await writeFile(
|
|
616
|
-
this.checksumsPath,
|
|
617
|
-
JSON.stringify(checksums, null, 2),
|
|
618
|
-
"utf-8"
|
|
619
|
-
);
|
|
620
|
-
console.log("checksum saved", this.checksumsPath);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
async getChecksumOfFile(filePath: string): Promise<string> {
|
|
624
|
-
return new Promise<string>((resolve, reject) => {
|
|
625
|
-
const hash = crypto.createHash("sha1");
|
|
626
|
-
const input = createReadStream(filePath);
|
|
627
|
-
input.on("error", reject);
|
|
628
|
-
input.on("data", function (chunk: any) {
|
|
629
|
-
hash.update(chunk);
|
|
630
|
-
});
|
|
631
|
-
input.on("close", function () {
|
|
632
|
-
resolve(hash.digest("hex"));
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
async readApisFromFile(filePath: string) {
|
|
638
|
-
const sourceFile = ts.createSourceFile(
|
|
639
|
-
filePath,
|
|
640
|
-
(await readFile(filePath)).toString(),
|
|
641
|
-
ts.ScriptTarget.Latest
|
|
642
|
-
);
|
|
643
|
-
|
|
644
|
-
const methods: Omit<ExtendedApi, "path" | "options">[] = [];
|
|
645
|
-
let modelName: string = "UnknownModel";
|
|
646
|
-
let methodName: string = "unknownMethod";
|
|
647
|
-
const visitor = (node: ts.Node) => {
|
|
648
|
-
if (ts.isClassDeclaration(node)) {
|
|
649
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
650
|
-
modelName = node.name.escapedText.toString().replace(/Class$/, "");
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
if (ts.isMethodDeclaration(node)) {
|
|
654
|
-
if (ts.isIdentifier(node.name)) {
|
|
655
|
-
methodName = node.name.escapedText.toString();
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const typeParameters: ApiParamType.TypeParam[] = (
|
|
659
|
-
node.typeParameters ?? []
|
|
660
|
-
).map((typeParam) => {
|
|
661
|
-
const tp = typeParam as ts.TypeParameterDeclaration;
|
|
662
|
-
|
|
663
|
-
return {
|
|
664
|
-
t: "type-param",
|
|
665
|
-
id: tp.name.escapedText.toString(),
|
|
666
|
-
constraint: tp.constraint
|
|
667
|
-
? this.resolveTypeNode(tp.constraint)
|
|
668
|
-
: undefined,
|
|
669
|
-
};
|
|
670
|
-
});
|
|
671
|
-
const parameters: ApiParam[] = node.parameters.map(
|
|
672
|
-
(paramDec, index) => {
|
|
673
|
-
const defaultDef = this.printNode(paramDec.initializer, sourceFile);
|
|
674
|
-
|
|
675
|
-
// 기본값이 있는 경우 paramDec.type가 undefined로 나옴
|
|
676
|
-
|
|
677
|
-
return this.resolveParamDec(
|
|
678
|
-
{
|
|
679
|
-
name: paramDec.name,
|
|
680
|
-
type: paramDec.type as ts.TypeNode,
|
|
681
|
-
optional:
|
|
682
|
-
paramDec.questionToken !== undefined ||
|
|
683
|
-
paramDec.initializer !== undefined,
|
|
684
|
-
defaultDef,
|
|
685
|
-
},
|
|
686
|
-
index
|
|
687
|
-
);
|
|
688
|
-
}
|
|
689
|
-
);
|
|
690
|
-
if (node.type === undefined) {
|
|
691
|
-
throw new Error(
|
|
692
|
-
`리턴 타입이 기재되지 않은 메소드 ${modelName}.${methodName}`
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
const returnType = this.resolveTypeNode(node.type!);
|
|
696
|
-
|
|
697
|
-
methods.push({
|
|
698
|
-
modelName,
|
|
699
|
-
methodName,
|
|
700
|
-
typeParameters,
|
|
701
|
-
parameters,
|
|
702
|
-
returnType,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
ts.forEachChild(node, visitor);
|
|
706
|
-
};
|
|
707
|
-
visitor(sourceFile);
|
|
708
|
-
|
|
709
|
-
if (methods.length === 0) {
|
|
710
|
-
return [];
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// 현재 파일의 등록된 API 필터
|
|
714
|
-
const currentModelApis = registeredApis.filter((api) => {
|
|
715
|
-
return methods.find(
|
|
716
|
-
(method) =>
|
|
717
|
-
method.modelName === api.modelName &&
|
|
718
|
-
method.methodName === api.methodName
|
|
719
|
-
);
|
|
720
|
-
});
|
|
721
|
-
if (currentModelApis.length === 0) {
|
|
722
|
-
// const p = path.join(tmpdir(), "sonamu-syncer-error.json");
|
|
723
|
-
// writeFileSync(p, JSON.stringify(registeredApis, null, 2));
|
|
724
|
-
// execSync(`open ${p}`);
|
|
725
|
-
throw new Error(`현재 파일에 사전 등록된 API가 없습니다. ${filePath}`);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// 등록된 API에 현재 메소드 타입 정보 확장
|
|
729
|
-
const extendedApis = currentModelApis.map((api) => {
|
|
730
|
-
const foundMethod = methods.find(
|
|
731
|
-
(method) =>
|
|
732
|
-
method.modelName === api.modelName &&
|
|
733
|
-
method.methodName === api.methodName
|
|
734
|
-
);
|
|
735
|
-
return {
|
|
736
|
-
...api,
|
|
737
|
-
typeParameters: foundMethod!.typeParameters,
|
|
738
|
-
parameters: foundMethod!.parameters,
|
|
739
|
-
returnType: foundMethod!.returnType,
|
|
740
|
-
};
|
|
741
|
-
});
|
|
742
|
-
return extendedApis;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
resolveTypeNode(typeNode: ts.TypeNode): ApiParamType {
|
|
746
|
-
switch (typeNode?.kind) {
|
|
747
|
-
case ts.SyntaxKind.AnyKeyword:
|
|
748
|
-
return "any";
|
|
749
|
-
case ts.SyntaxKind.UnknownKeyword:
|
|
750
|
-
return "unknown";
|
|
751
|
-
case ts.SyntaxKind.StringKeyword:
|
|
752
|
-
return "string";
|
|
753
|
-
case ts.SyntaxKind.NumberKeyword:
|
|
754
|
-
return "number";
|
|
755
|
-
case ts.SyntaxKind.BooleanKeyword:
|
|
756
|
-
return "boolean";
|
|
757
|
-
case ts.SyntaxKind.UndefinedKeyword:
|
|
758
|
-
return "undefined";
|
|
759
|
-
case ts.SyntaxKind.NullKeyword:
|
|
760
|
-
return "null";
|
|
761
|
-
case ts.SyntaxKind.VoidKeyword:
|
|
762
|
-
return "void";
|
|
763
|
-
case ts.SyntaxKind.LiteralType:
|
|
764
|
-
const literal = (typeNode as ts.LiteralTypeNode).literal;
|
|
765
|
-
if (ts.isStringLiteral(literal)) {
|
|
766
|
-
return {
|
|
767
|
-
t: "string-literal",
|
|
768
|
-
value: literal.text,
|
|
769
|
-
};
|
|
770
|
-
} else if (ts.isNumericLiteral(literal)) {
|
|
771
|
-
return {
|
|
772
|
-
t: "numeric-literal",
|
|
773
|
-
value: Number(literal.text),
|
|
774
|
-
};
|
|
775
|
-
} else {
|
|
776
|
-
if (literal.kind === ts.SyntaxKind.NullKeyword) {
|
|
777
|
-
return "null";
|
|
778
|
-
} else if (literal.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
779
|
-
return "undefined";
|
|
780
|
-
} else if (literal.kind === ts.SyntaxKind.TrueKeyword) {
|
|
781
|
-
return "true";
|
|
782
|
-
} else if (literal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
783
|
-
return "false";
|
|
784
|
-
}
|
|
785
|
-
throw new Error("알 수 없는 리터럴");
|
|
786
|
-
}
|
|
787
|
-
case ts.SyntaxKind.ArrayType:
|
|
788
|
-
const arrNode = typeNode as ts.ArrayTypeNode;
|
|
789
|
-
return {
|
|
790
|
-
t: "array",
|
|
791
|
-
elementsType: this.resolveTypeNode(arrNode.elementType),
|
|
792
|
-
};
|
|
793
|
-
case ts.SyntaxKind.TypeLiteral:
|
|
794
|
-
const literalNode = typeNode as ts.TypeLiteralNode;
|
|
795
|
-
return {
|
|
796
|
-
t: "object",
|
|
797
|
-
props: literalNode.members.map((member) => {
|
|
798
|
-
if (ts.isIndexSignatureDeclaration(member)) {
|
|
799
|
-
assert(member.parameters[0]);
|
|
800
|
-
const res = this.resolveParamDec({
|
|
801
|
-
name: member.parameters[0].name as ts.Identifier,
|
|
802
|
-
type: member.parameters[0].type as ts.TypeNode,
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
return this.resolveParamDec({
|
|
806
|
-
name: {
|
|
807
|
-
escapedText: `[${res.name}${res.optional ? "?" : ""}: ${
|
|
808
|
-
res.type
|
|
809
|
-
}]`,
|
|
810
|
-
} as ts.Identifier,
|
|
811
|
-
type: member.type as ts.TypeNode,
|
|
812
|
-
});
|
|
813
|
-
} else {
|
|
814
|
-
return this.resolveParamDec({
|
|
815
|
-
name: (member as ts.PropertySignature).name as ts.Identifier,
|
|
816
|
-
type: (member as ts.PropertySignature).type as ts.TypeNode,
|
|
817
|
-
optional:
|
|
818
|
-
(member as ts.PropertySignature).questionToken !== undefined,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
}),
|
|
822
|
-
};
|
|
823
|
-
case ts.SyntaxKind.TypeReference:
|
|
824
|
-
return {
|
|
825
|
-
t: "ref",
|
|
826
|
-
id: (
|
|
827
|
-
(typeNode as ts.TypeReferenceNode).typeName as ts.Identifier
|
|
828
|
-
).escapedText.toString(),
|
|
829
|
-
args: (typeNode as ts.TypeReferenceNode).typeArguments?.map(
|
|
830
|
-
(typeArg) => this.resolveTypeNode(typeArg)
|
|
831
|
-
),
|
|
832
|
-
};
|
|
833
|
-
case ts.SyntaxKind.UnionType:
|
|
834
|
-
return {
|
|
835
|
-
t: "union",
|
|
836
|
-
types: (typeNode as ts.UnionTypeNode).types.map((type) =>
|
|
837
|
-
this.resolveTypeNode(type)
|
|
838
|
-
),
|
|
839
|
-
};
|
|
840
|
-
case ts.SyntaxKind.IntersectionType:
|
|
841
|
-
return {
|
|
842
|
-
t: "intersection",
|
|
843
|
-
types: (typeNode as ts.IntersectionTypeNode).types.map((type) =>
|
|
844
|
-
this.resolveTypeNode(type)
|
|
845
|
-
),
|
|
846
|
-
};
|
|
847
|
-
case ts.SyntaxKind.IndexedAccessType:
|
|
848
|
-
return {
|
|
849
|
-
t: "indexed-access",
|
|
850
|
-
object: this.resolveTypeNode(
|
|
851
|
-
(typeNode as ts.IndexedAccessTypeNode).objectType
|
|
852
|
-
),
|
|
853
|
-
index: this.resolveTypeNode(
|
|
854
|
-
(typeNode as ts.IndexedAccessTypeNode).indexType
|
|
855
|
-
),
|
|
856
|
-
};
|
|
857
|
-
case ts.SyntaxKind.TupleType:
|
|
858
|
-
if (ts.isTupleTypeNode(typeNode)) {
|
|
859
|
-
return {
|
|
860
|
-
t: "tuple-type",
|
|
861
|
-
elements: typeNode.elements.map((elem) =>
|
|
862
|
-
this.resolveTypeNode(elem)
|
|
863
|
-
),
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
break;
|
|
867
|
-
case undefined:
|
|
868
|
-
throw new Error(`typeNode undefined`);
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
console.debug(typeNode);
|
|
872
|
-
throw new Error(`알 수 없는 SyntaxKind ${typeNode.kind}`);
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
resolveParamDec = (
|
|
876
|
-
paramDec: {
|
|
877
|
-
name: ts.BindingName;
|
|
878
|
-
type: ts.TypeNode;
|
|
879
|
-
optional?: boolean;
|
|
880
|
-
defaultDef?: string;
|
|
881
|
-
},
|
|
882
|
-
index: number = 0
|
|
883
|
-
): ApiParam => {
|
|
884
|
-
const name = paramDec.name as ts.Identifier;
|
|
885
|
-
const type = this.resolveTypeNode(paramDec.type);
|
|
886
|
-
|
|
887
|
-
if (name === undefined) {
|
|
888
|
-
console.debug({ name, type, paramDec });
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const result: ApiParam = {
|
|
892
|
-
name: name.escapedText ? name.escapedText.toString() : `nonameAt${index}`,
|
|
893
|
-
type,
|
|
894
|
-
optional: paramDec.optional === true,
|
|
895
|
-
defaultDef: paramDec?.defaultDef,
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
// 구조분해할당의 경우 타입이름 사용
|
|
899
|
-
if (
|
|
900
|
-
ts.isObjectBindingPattern(name) &&
|
|
901
|
-
ts.isTypeReferenceNode(paramDec.type) &&
|
|
902
|
-
ts.isIdentifier(paramDec.type.typeName)
|
|
903
|
-
) {
|
|
904
|
-
result.name = inflection.camelize(paramDec.type.typeName.text, true);
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return result;
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
printNode(
|
|
911
|
-
node: ts.Node | undefined,
|
|
912
|
-
sourceFile: ts.SourceFile
|
|
913
|
-
): string | undefined {
|
|
914
|
-
if (node === undefined) {
|
|
915
|
-
return undefined;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
919
|
-
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
async autoloadApis() {
|
|
923
|
-
const pathPattern = path.join(
|
|
924
|
-
Sonamu.apiRootPath,
|
|
925
|
-
"/src/application/**/*.{model,frame}.ts"
|
|
926
|
-
);
|
|
927
|
-
// console.debug(chalk.yellow(`autoload:APIs @ ${pathPattern}`));
|
|
928
|
-
|
|
929
|
-
const filePaths = await globAsync(pathPattern);
|
|
930
|
-
const result = await Promise.all(
|
|
931
|
-
filePaths.map((filePath) => this.readApisFromFile(filePath))
|
|
932
|
-
);
|
|
933
|
-
this.apis = result.flat();
|
|
934
|
-
return this.apis;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
async autoloadModels(): Promise<{ [modelName: string]: unknown }> {
|
|
938
|
-
const pathPattern = path.join(
|
|
939
|
-
Sonamu.apiRootPath,
|
|
940
|
-
"dist/application/**/*.{model,frame}.js"
|
|
941
|
-
);
|
|
942
|
-
// console.debug(chalk.yellow(`autoload:models @ ${pathPattern}`));
|
|
943
|
-
|
|
944
|
-
const filePaths = await filterAsync(
|
|
945
|
-
await globAsync(pathPattern),
|
|
946
|
-
async (path) => {
|
|
947
|
-
// src 디렉터리 내에 있는 해당 파일이 존재할 경우에만 로드
|
|
948
|
-
// 삭제된 파일이지만 dist에 남아있는 경우 BaseSchema undefined 에러 방지
|
|
949
|
-
const srcPath = path.replace("/dist/", "/src/").replace(".js", ".ts");
|
|
950
|
-
return await exists(srcPath);
|
|
951
|
-
}
|
|
952
|
-
);
|
|
953
|
-
const modules = await importMultiple(filePaths);
|
|
954
|
-
const functions = modules
|
|
955
|
-
.map(({ imported }) => Object.entries(imported))
|
|
956
|
-
.flat();
|
|
957
|
-
this.models = Object.fromEntries(
|
|
958
|
-
functions.filter(
|
|
959
|
-
([name]) => name.endsWith("Model") || name.endsWith("Frame")
|
|
960
|
-
)
|
|
961
|
-
);
|
|
962
|
-
return this.models;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
async autoloadTypes(
|
|
966
|
-
doRefresh: boolean = false
|
|
967
|
-
): Promise<{ [typeName: string]: z.ZodObject<any> }> {
|
|
968
|
-
if (!doRefresh && Object.keys(this.types).length > 0) {
|
|
969
|
-
return this.types;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const pathPatterns = [
|
|
973
|
-
path.join(Sonamu.apiRootPath, "/dist/application/**/*.types.js"),
|
|
974
|
-
path.join(Sonamu.apiRootPath, "/dist/application/**/*.generated.js"),
|
|
975
|
-
];
|
|
976
|
-
// console.debug(chalk.magenta(`autoload:types @ ${pathPatterns.join("\n")}`));
|
|
977
|
-
|
|
978
|
-
const filePaths = await filterAsync(
|
|
979
|
-
(await mapAsync(pathPatterns, globAsync)).flat(),
|
|
980
|
-
async (path) => {
|
|
981
|
-
// src 디렉터리 내에 있는 해당 파일이 존재할 경우에만 로드
|
|
982
|
-
// 삭제된 파일이지만 dist에 남아있는 경우 BaseSchema undefined 에러 방지
|
|
983
|
-
const srcPath = path.replace("/dist/", "/src/").replace(".js", ".ts");
|
|
984
|
-
return await exists(srcPath);
|
|
985
|
-
}
|
|
986
|
-
);
|
|
987
|
-
const modules = await importMultiple(filePaths, doRefresh);
|
|
988
|
-
const functions = modules
|
|
989
|
-
.map(({ imported }) => Object.entries(imported))
|
|
990
|
-
.flat();
|
|
991
|
-
this.types = Object.fromEntries(
|
|
992
|
-
functions.filter(([, f]) => f instanceof z.ZodType)
|
|
993
|
-
) as typeof this.types;
|
|
994
|
-
return this.types;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
getTemplate(key: TemplateKey): Template {
|
|
998
|
-
if (key === "entity") {
|
|
999
|
-
return new Template__entity();
|
|
1000
|
-
} else if (key === "init_types") {
|
|
1001
|
-
return new Template__init_types();
|
|
1002
|
-
} else if (key === "generated") {
|
|
1003
|
-
return new Template__generated();
|
|
1004
|
-
} else if (key === "generated_sso") {
|
|
1005
|
-
return new Template__generated_sso();
|
|
1006
|
-
} else if (key === "generated_http") {
|
|
1007
|
-
return new Template__generated_http();
|
|
1008
|
-
} else if (key === "model") {
|
|
1009
|
-
return new Template__model();
|
|
1010
|
-
} else if (key === "model_test") {
|
|
1011
|
-
return new Template__model_test();
|
|
1012
|
-
} else if (key === "service") {
|
|
1013
|
-
return new Template__service();
|
|
1014
|
-
} else if (key === "view_list") {
|
|
1015
|
-
return new Template__view_list();
|
|
1016
|
-
} else if (key === "view_list_columns") {
|
|
1017
|
-
return new Template__view_list_columns();
|
|
1018
|
-
} else if (key === "view_search_input") {
|
|
1019
|
-
return new Template__view_search_input();
|
|
1020
|
-
} else if (key === "view_form") {
|
|
1021
|
-
return new Template__view_form();
|
|
1022
|
-
} else if (key === "view_id_all_select") {
|
|
1023
|
-
return new Template__view_id_all_select();
|
|
1024
|
-
} else if (key === "view_id_async_select") {
|
|
1025
|
-
return new Template__view_id_async_select();
|
|
1026
|
-
} else if (key === "view_enums_select") {
|
|
1027
|
-
return new Template__view_enums_select();
|
|
1028
|
-
} else if (key === "view_enums_dropdown") {
|
|
1029
|
-
return new Template__view_enums_dropdown();
|
|
1030
|
-
} else if (key === "view_enums_buttonset") {
|
|
1031
|
-
return new Template__view_enums_buttonset();
|
|
1032
|
-
} else {
|
|
1033
|
-
throw new BadRequestException(`잘못된 템플릿 키 ${key}`);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
async renderTemplate<T extends keyof TemplateOptions>(
|
|
1038
|
-
key: T,
|
|
1039
|
-
options: TemplateOptions[T]
|
|
1040
|
-
): Promise<PathAndCode[]> {
|
|
1041
|
-
const template: Template = this.getTemplate(key);
|
|
1042
|
-
|
|
1043
|
-
let extra: unknown[] = [];
|
|
1044
|
-
if (key === "service") {
|
|
1045
|
-
// service 필요 정보 (API 리스트)
|
|
1046
|
-
const { modelTsPath } = options as TemplateOptions["service"];
|
|
1047
|
-
extra = [await this.readApisFromFile(modelTsPath)];
|
|
1048
|
-
} else if (["model", "view_list", "view_form"].includes(key)) {
|
|
1049
|
-
const entityId = (options as TemplateOptions["model"]).entityId;
|
|
1050
|
-
if (key === "view_list" || key === "model") {
|
|
1051
|
-
// view_list 필요 정보 (컬럼 노드, 리스트파라미터 노드)
|
|
1052
|
-
const columnsNode = await this.getColumnsNode(entityId, "A");
|
|
1053
|
-
const listParamsZodType = await this.getZodTypeById(
|
|
1054
|
-
`${entityId}ListParams`
|
|
1055
|
-
);
|
|
1056
|
-
const listParamsNode = this.zodTypeToRenderingNode(listParamsZodType);
|
|
1057
|
-
extra = [columnsNode, listParamsNode];
|
|
1058
|
-
} else if (key === "view_form") {
|
|
1059
|
-
// view_form 필요 정보 (세이브파라미터 노드)
|
|
1060
|
-
const saveParamsZodType = await this.getZodTypeById(
|
|
1061
|
-
`${entityId}SaveParams`
|
|
1062
|
-
);
|
|
1063
|
-
const saveParamsNode = this.zodTypeToRenderingNode(saveParamsZodType);
|
|
1064
|
-
extra = [saveParamsNode];
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const rendered = await template.render(options, ...extra);
|
|
1069
|
-
const resolved = await this.resolveRenderedTemplate(key, rendered);
|
|
1070
|
-
|
|
1071
|
-
let preTemplateResolved: PathAndCode[] = [];
|
|
1072
|
-
if (rendered.preTemplates) {
|
|
1073
|
-
preTemplateResolved = (
|
|
1074
|
-
await Promise.all(
|
|
1075
|
-
rendered.preTemplates.map(({ key, options }) => {
|
|
1076
|
-
return this.renderTemplate(key, options);
|
|
1077
|
-
})
|
|
1078
|
-
)
|
|
1079
|
-
).flat();
|
|
431
|
+
private async copyFileWithReplaceCoreToShared(
|
|
432
|
+
fromPath: string,
|
|
433
|
+
toPath: string
|
|
434
|
+
) {
|
|
435
|
+
if (!(await exists(fromPath))) {
|
|
436
|
+
return;
|
|
1080
437
|
}
|
|
1081
438
|
|
|
1082
|
-
|
|
1083
|
-
}
|
|
439
|
+
const oldFileContent = (await readFile(fromPath)).toString();
|
|
1084
440
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const { target, path: filePath, body, importKeys, customHeaders } = result;
|
|
1090
|
-
|
|
1091
|
-
// import 할 대상의 대상 path 추출
|
|
1092
|
-
const importDefs = importKeys
|
|
1093
|
-
.reduce(
|
|
1094
|
-
(r, importKey) => {
|
|
1095
|
-
const modulePath = EntityManager.getModulePath(importKey);
|
|
1096
|
-
let importPath = modulePath;
|
|
1097
|
-
if (modulePath.includes("/") || modulePath.includes(".")) {
|
|
1098
|
-
importPath = wrapIf(
|
|
1099
|
-
path.relative(path.dirname(filePath), modulePath),
|
|
1100
|
-
(p) => [p.startsWith(".") === false, "./" + p]
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
// 같은 파일에서 import 하는 경우 keys 로 나열 처리
|
|
1105
|
-
const existsOne = r.find(
|
|
1106
|
-
(importDef) => importDef.from === importPath
|
|
1107
|
-
);
|
|
1108
|
-
if (existsOne) {
|
|
1109
|
-
existsOne.keys = _.uniq(existsOne.keys.concat(importKey));
|
|
1110
|
-
} else {
|
|
1111
|
-
r.push({
|
|
1112
|
-
keys: [importKey],
|
|
1113
|
-
from: importPath,
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
return r;
|
|
1117
|
-
},
|
|
1118
|
-
[] as {
|
|
1119
|
-
keys: string[];
|
|
1120
|
-
from: string;
|
|
1121
|
-
}[]
|
|
1122
|
-
)
|
|
1123
|
-
// 셀프 참조 방지
|
|
1124
|
-
.filter(
|
|
1125
|
-
(importDef) =>
|
|
1126
|
-
filePath.endsWith(importDef.from.replace("./", "") + ".ts") === false
|
|
441
|
+
const newFileContent = (() => {
|
|
442
|
+
const nfc = oldFileContent.replace(
|
|
443
|
+
/from "sonamu"/g,
|
|
444
|
+
`from "src/services/sonamu.shared"`
|
|
1127
445
|
);
|
|
1128
446
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
...(customHeaders ?? []),
|
|
1132
|
-
...importDefs.map(
|
|
1133
|
-
(importDef) =>
|
|
1134
|
-
`import { ${importDef.keys.join(", ")} } from '${importDef.from}'`
|
|
1135
|
-
),
|
|
1136
|
-
].join("\n");
|
|
1137
|
-
|
|
1138
|
-
const formatted = await (async () => {
|
|
1139
|
-
if (key === "generated_http") {
|
|
1140
|
-
return [header, body].join("\n\n");
|
|
1141
|
-
} else {
|
|
1142
|
-
return prettier.format([header, body].join("\n\n"), {
|
|
1143
|
-
parser: key === "entity" ? "json" : "typescript",
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
})();
|
|
1147
|
-
|
|
1148
|
-
return {
|
|
1149
|
-
path: target + "/" + filePath,
|
|
1150
|
-
code: formatted,
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
async writeCodeToPath(pathAndCode: PathAndCode): Promise<string[]> {
|
|
1155
|
-
const { targets } = Sonamu.config.sync;
|
|
1156
|
-
const { appRootPath } = Sonamu;
|
|
1157
|
-
const filePath = `${Sonamu.appRootPath}/${pathAndCode.path}`;
|
|
1158
|
-
|
|
1159
|
-
const dstFilePaths = _.uniq(
|
|
1160
|
-
targets.map((target) => filePath.replace("/:target/", `/${target}/`))
|
|
1161
|
-
);
|
|
1162
|
-
return await Promise.all(
|
|
1163
|
-
dstFilePaths.map(async (dstFilePath) => {
|
|
1164
|
-
const dir = path.dirname(dstFilePath);
|
|
1165
|
-
if (!(await exists(dir))) {
|
|
1166
|
-
await mkdir(dir, { recursive: true });
|
|
1167
|
-
}
|
|
1168
|
-
await writeFile(dstFilePath, pathAndCode.code);
|
|
1169
|
-
console.log(
|
|
1170
|
-
chalk.bold("Generated: ") +
|
|
1171
|
-
chalk.blue(`${dstFilePath.replace(appRootPath + "/", "")}`)
|
|
1172
|
-
);
|
|
1173
|
-
return dstFilePath;
|
|
1174
|
-
})
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
async generateTemplate(
|
|
1179
|
-
key: TemplateKey,
|
|
1180
|
-
templateOptions: any,
|
|
1181
|
-
_generateOptions?: GenerateOptions
|
|
1182
|
-
) {
|
|
1183
|
-
const generateOptions = {
|
|
1184
|
-
overwrite: false,
|
|
1185
|
-
..._generateOptions,
|
|
1186
|
-
};
|
|
1187
|
-
|
|
1188
|
-
// 키 children
|
|
1189
|
-
const keys: TemplateKey[] = [key];
|
|
1190
|
-
|
|
1191
|
-
// 템플릿 렌더
|
|
1192
|
-
const pathAndCodes = (
|
|
1193
|
-
await Promise.all(
|
|
1194
|
-
keys.map(async (key) => {
|
|
1195
|
-
return await this.renderTemplate(key, templateOptions);
|
|
1196
|
-
})
|
|
1197
|
-
)
|
|
1198
|
-
).flat();
|
|
1199
|
-
|
|
1200
|
-
const filteredPathAndCodes: PathAndCode[] = await (async () => {
|
|
1201
|
-
if (generateOptions.overwrite === true) {
|
|
1202
|
-
return pathAndCodes;
|
|
447
|
+
if (toPath.includes("/web/")) {
|
|
448
|
+
return nfc; // .replace(/from "lodash";/g, `from "lodash-es";`); // TODO 흠? 필요없을듯.
|
|
1203
449
|
} else {
|
|
1204
|
-
return
|
|
1205
|
-
const { targets } = Sonamu.config.sync;
|
|
1206
|
-
const filePath = `${Sonamu.appRootPath}/${pathAndCode.path}`;
|
|
1207
|
-
const dstFilePaths = targets.map((target) =>
|
|
1208
|
-
filePath.replace("/:target/", `/${target}/`)
|
|
1209
|
-
);
|
|
1210
|
-
return await everyAsync(
|
|
1211
|
-
dstFilePaths,
|
|
1212
|
-
async (dstPath) => !(await exists(dstPath))
|
|
1213
|
-
);
|
|
1214
|
-
});
|
|
450
|
+
return nfc;
|
|
1215
451
|
}
|
|
1216
452
|
})();
|
|
1217
|
-
|
|
1218
|
-
throw new AlreadyProcessedException(
|
|
1219
|
-
"이미 경로에 모든 파일이 존재합니다."
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
return Promise.all(
|
|
1224
|
-
filteredPathAndCodes.map((pathAndCode) =>
|
|
1225
|
-
this.writeCodeToPath(pathAndCode)
|
|
1226
|
-
)
|
|
1227
|
-
);
|
|
453
|
+
return writeFile(toPath, newFileContent);
|
|
1228
454
|
}
|
|
1229
455
|
|
|
456
|
+
/**
|
|
457
|
+
* 주어진 엔티티와 템플릿 키에 대해, 생성된 코드가 존재하는지 확인합니다.
|
|
458
|
+
* @param entityId 엔티티 ID
|
|
459
|
+
* @param templateKey 템플릿 키
|
|
460
|
+
* @param enumId 열거형 ID
|
|
461
|
+
* @returns 생성된 코드가 존재하는지 여부
|
|
462
|
+
*/
|
|
1230
463
|
async checkExistsGenCode(
|
|
1231
464
|
entityId: string,
|
|
1232
465
|
templateKey: TemplateKey,
|
|
1233
466
|
enumId?: string
|
|
1234
467
|
): Promise<{ subPath: string; fullPath: string; isExists: boolean }> {
|
|
1235
|
-
const { target, path: genPath } =
|
|
468
|
+
const { target, path: genPath } = Template.find(
|
|
1236
469
|
templateKey
|
|
1237
470
|
).getTargetAndPath(EntityManager.getNamesFromId(entityId), enumId);
|
|
1238
471
|
|
|
1239
|
-
const fullPath = path.join(Sonamu.appRootPath, target, genPath);
|
|
1240
472
|
const subPath = path.join(target, genPath);
|
|
473
|
+
const fullPath = path.join(Sonamu.appRootPath, subPath);
|
|
1241
474
|
return {
|
|
1242
475
|
subPath,
|
|
1243
476
|
fullPath,
|
|
@@ -1245,6 +478,12 @@ export class Syncer {
|
|
|
1245
478
|
};
|
|
1246
479
|
}
|
|
1247
480
|
|
|
481
|
+
/**
|
|
482
|
+
* 주어진 엔티티와 열거형에 대해, 생성된 코드가 존재하는지 확인합니다.
|
|
483
|
+
* @param entityId 엔티티 ID
|
|
484
|
+
* @param enums 열거형 레이블
|
|
485
|
+
* @returns 생성된 코드가 존재하는지 여부
|
|
486
|
+
*/
|
|
1248
487
|
async checkExists(
|
|
1249
488
|
entityId: string,
|
|
1250
489
|
enums: {
|
|
@@ -1260,7 +499,7 @@ export class Syncer {
|
|
|
1260
499
|
return await reduceAsync(
|
|
1261
500
|
keys,
|
|
1262
501
|
async (result, key) => {
|
|
1263
|
-
const tpl =
|
|
502
|
+
const tpl = Template.find(key);
|
|
1264
503
|
if (key.startsWith("view_enums")) {
|
|
1265
504
|
await mapAsync(enumsKeys, async (componentId) => {
|
|
1266
505
|
const { target, path: p } = tpl.getTargetAndPath(
|
|
@@ -1292,350 +531,54 @@ export class Syncer {
|
|
|
1292
531
|
);
|
|
1293
532
|
}
|
|
1294
533
|
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1297
|
-
const moduleAbsPath = path.join(
|
|
1298
|
-
Sonamu.apiRootPath,
|
|
1299
|
-
"dist",
|
|
1300
|
-
"application",
|
|
1301
|
-
modulePath + ".js"
|
|
1302
|
-
);
|
|
1303
|
-
const importPath = "./" + path.relative(__dirname, moduleAbsPath);
|
|
1304
|
-
const imported = await import(importPath);
|
|
1305
|
-
|
|
1306
|
-
if (!imported[zodTypeId]) {
|
|
1307
|
-
throw new Error(`존재하지 않는 zodTypeId ${zodTypeId}`);
|
|
1308
|
-
}
|
|
1309
|
-
return imported[zodTypeId].describe(zodTypeId);
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
async propNodeToZodType(propNode: EntityPropNode): Promise<z.ZodTypeAny> {
|
|
1313
|
-
if (propNode.nodeType === "plain") {
|
|
1314
|
-
return this.propToZodType(propNode.prop);
|
|
1315
|
-
} else if (propNode.nodeType === "array") {
|
|
1316
|
-
if (propNode.prop === undefined) {
|
|
1317
|
-
throw new Error();
|
|
1318
|
-
} else if (propNode.children.length > 0) {
|
|
1319
|
-
return (
|
|
1320
|
-
await this.propNodeToZodType({
|
|
1321
|
-
...propNode,
|
|
1322
|
-
nodeType: "object",
|
|
1323
|
-
})
|
|
1324
|
-
).array();
|
|
1325
|
-
} else {
|
|
1326
|
-
const innerType = await this.propToZodType(propNode.prop);
|
|
1327
|
-
if (propNode.prop.nullable === true) {
|
|
1328
|
-
return z.array(innerType).nullable();
|
|
1329
|
-
} else {
|
|
1330
|
-
return z.array(innerType);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
} else if (propNode.nodeType === "object") {
|
|
1334
|
-
const obj = await propNode.children.reduce(
|
|
1335
|
-
async (promise, childPropNode) => {
|
|
1336
|
-
const result = await promise;
|
|
1337
|
-
result[childPropNode.prop!.name] =
|
|
1338
|
-
await this.propNodeToZodType(childPropNode);
|
|
1339
|
-
return result;
|
|
1340
|
-
},
|
|
1341
|
-
{} as any
|
|
1342
|
-
);
|
|
1343
|
-
|
|
1344
|
-
if (propNode.prop?.nullable === true) {
|
|
1345
|
-
return z.object(obj).nullable();
|
|
1346
|
-
} else {
|
|
1347
|
-
return z.object(obj);
|
|
1348
|
-
}
|
|
1349
|
-
} else {
|
|
1350
|
-
throw Error;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
async propToZodType(prop: EntityProp): Promise<z.ZodTypeAny> {
|
|
1354
|
-
let zodType: z.ZodTypeAny = z.unknown();
|
|
1355
|
-
if (isIntegerProp(prop)) {
|
|
1356
|
-
zodType = z.number().int();
|
|
1357
|
-
} else if (isBigIntegerProp(prop)) {
|
|
1358
|
-
zodType = z.bigint();
|
|
1359
|
-
} else if (isTextProp(prop)) {
|
|
1360
|
-
zodType = z.string().max(getTextTypeLength(prop.textType));
|
|
1361
|
-
} else if (isEnumProp(prop)) {
|
|
1362
|
-
zodType = await this.getZodTypeById(prop.id);
|
|
1363
|
-
} else if (isStringProp(prop)) {
|
|
1364
|
-
zodType = z.string().max(prop.length);
|
|
1365
|
-
} else if (isFloatProp(prop) || isDoubleProp(prop)) {
|
|
1366
|
-
zodType = z.number();
|
|
1367
|
-
} else if (isDecimalProp(prop)) {
|
|
1368
|
-
zodType = z.string();
|
|
1369
|
-
} else if (isBooleanProp(prop)) {
|
|
1370
|
-
zodType = z.boolean();
|
|
1371
|
-
} else if (isDateProp(prop)) {
|
|
1372
|
-
zodType = z.string().length(10);
|
|
1373
|
-
} else if (isTimeProp(prop)) {
|
|
1374
|
-
zodType = z.string().length(8);
|
|
1375
|
-
} else if (isDateTimeProp(prop)) {
|
|
1376
|
-
zodType = z.date();
|
|
1377
|
-
} else if (isTimestampProp(prop)) {
|
|
1378
|
-
zodType = z.date();
|
|
1379
|
-
} else if (isJsonProp(prop)) {
|
|
1380
|
-
zodType = await this.getZodTypeById(prop.id);
|
|
1381
|
-
} else if (isUuidProp(prop)) {
|
|
1382
|
-
zodType = z.uuid();
|
|
1383
|
-
} else if (isVirtualProp(prop)) {
|
|
1384
|
-
zodType = await this.getZodTypeById(prop.id);
|
|
1385
|
-
} else if (isRelationProp(prop)) {
|
|
1386
|
-
if (
|
|
1387
|
-
isBelongsToOneRelationProp(prop) ||
|
|
1388
|
-
(isOneToOneRelationProp(prop) && prop.hasJoinColumn)
|
|
1389
|
-
) {
|
|
1390
|
-
zodType = z.number().int();
|
|
1391
|
-
}
|
|
1392
|
-
} else {
|
|
1393
|
-
throw new Error(`prop을 zodType으로 변환하는데 실패 ${prop}}`);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
if ((prop as { unsigned?: boolean }).unsigned) {
|
|
1397
|
-
zodType = (zodType as z.ZodNumber).nonnegative();
|
|
1398
|
-
}
|
|
1399
|
-
if (prop.nullable) {
|
|
1400
|
-
zodType = zodType.nullable();
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
return zodType;
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
resolveRenderType(
|
|
1407
|
-
key: string,
|
|
1408
|
-
zodType: z.ZodTypeAny
|
|
1409
|
-
): RenderingNode["renderType"] {
|
|
1410
|
-
if (zodType instanceof z.ZodDate) {
|
|
1411
|
-
return "datetime";
|
|
1412
|
-
} else if (zodType instanceof z.ZodString) {
|
|
1413
|
-
if (key.includes("img") || key.includes("image")) {
|
|
1414
|
-
return "string-image";
|
|
1415
|
-
} else if (zodType.description === "SQLDateTimeString") {
|
|
1416
|
-
return "string-datetime";
|
|
1417
|
-
} else if (key.endsWith("date")) {
|
|
1418
|
-
return "string-date";
|
|
1419
|
-
} else {
|
|
1420
|
-
return "string-plain";
|
|
1421
|
-
}
|
|
1422
|
-
} else if (zodType instanceof z.ZodNumber) {
|
|
1423
|
-
if (key === "id") {
|
|
1424
|
-
return "number-id";
|
|
1425
|
-
} else if (key.endsWith("_id")) {
|
|
1426
|
-
return "number-fk_id";
|
|
1427
|
-
} else {
|
|
1428
|
-
return "number-plain";
|
|
1429
|
-
}
|
|
1430
|
-
} else if (zodType instanceof z.ZodBoolean) {
|
|
1431
|
-
return "boolean";
|
|
1432
|
-
} else if (zodType instanceof z.ZodEnum) {
|
|
1433
|
-
return "enums";
|
|
1434
|
-
} else if (zodType instanceof z.ZodRecord) {
|
|
1435
|
-
return "record";
|
|
1436
|
-
} else if (zodType instanceof z.ZodAny || zodType instanceof z.ZodUnknown) {
|
|
1437
|
-
return "string-plain";
|
|
1438
|
-
} else if (zodType instanceof z.ZodUnion) {
|
|
1439
|
-
return "string-plain";
|
|
1440
|
-
} else if (zodType instanceof z.ZodLiteral) {
|
|
1441
|
-
return "string-plain";
|
|
1442
|
-
} else {
|
|
1443
|
-
throw new Error(`타입 파싱 불가 ${key} ${zodType.def.type}`);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
zodTypeToRenderingNode(
|
|
1448
|
-
zodType: z.ZodType<any>,
|
|
1449
|
-
baseKey: string = "root"
|
|
1450
|
-
): RenderingNode {
|
|
1451
|
-
const def = {
|
|
1452
|
-
name: baseKey,
|
|
1453
|
-
label: inflection.camelize(baseKey, false),
|
|
1454
|
-
zodType,
|
|
1455
|
-
};
|
|
1456
|
-
if (zodType instanceof z.ZodObject) {
|
|
1457
|
-
const columnKeys = Object.keys(zodType.shape);
|
|
1458
|
-
const children = columnKeys.map((key) => {
|
|
1459
|
-
const innerType = zodType.shape[key];
|
|
1460
|
-
return this.zodTypeToRenderingNode(innerType, key);
|
|
1461
|
-
});
|
|
1462
|
-
return {
|
|
1463
|
-
...def,
|
|
1464
|
-
renderType: "object",
|
|
1465
|
-
children,
|
|
1466
|
-
};
|
|
1467
|
-
} else if (zodType instanceof z.ZodArray) {
|
|
1468
|
-
const innerType = (zodType as z.ZodArray<z.ZodType<any>>).def.element;
|
|
1469
|
-
if (innerType instanceof z.ZodString && baseKey.includes("images")) {
|
|
1470
|
-
return {
|
|
1471
|
-
...def,
|
|
1472
|
-
renderType: "array-images",
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
return {
|
|
1476
|
-
...def,
|
|
1477
|
-
renderType: "array",
|
|
1478
|
-
element: this.zodTypeToRenderingNode(innerType, baseKey),
|
|
1479
|
-
};
|
|
1480
|
-
} else if (zodType instanceof z.ZodUnion) {
|
|
1481
|
-
const optionNodes = (zodType as z.ZodUnion<z.ZodType[]>).def.options.map((opt) =>
|
|
1482
|
-
this.zodTypeToRenderingNode(opt, baseKey)
|
|
1483
|
-
);
|
|
1484
|
-
// TODO: ZodUnion이 들어있는 경우 핸들링
|
|
1485
|
-
return optionNodes[0];
|
|
1486
|
-
} else if (zodType instanceof z.ZodOptional) {
|
|
1487
|
-
return {
|
|
1488
|
-
...this.zodTypeToRenderingNode((zodType as z.ZodOptional<z.ZodType>).def.innerType, baseKey),
|
|
1489
|
-
optional: true,
|
|
1490
|
-
};
|
|
1491
|
-
} else if (zodType instanceof z.ZodNullable) {
|
|
1492
|
-
return {
|
|
1493
|
-
...this.zodTypeToRenderingNode((zodType as z.ZodNullable<z.ZodType>).def.innerType, baseKey),
|
|
1494
|
-
nullable: true,
|
|
1495
|
-
};
|
|
1496
|
-
} else {
|
|
1497
|
-
return {
|
|
1498
|
-
...def,
|
|
1499
|
-
renderType: this.resolveRenderType(baseKey, zodType),
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
async getColumnsNode(
|
|
1505
|
-
entityId: string,
|
|
1506
|
-
subsetKey: string
|
|
1507
|
-
): Promise<RenderingNode> {
|
|
1508
|
-
const entity = EntityManager.get(entityId);
|
|
1509
|
-
const subsetA = entity.subsets[subsetKey];
|
|
1510
|
-
if (subsetA === undefined) {
|
|
1511
|
-
throw new ServiceUnavailableException("SubsetA 가 없습니다.");
|
|
1512
|
-
}
|
|
1513
|
-
const propNodes = entity.fieldExprsToPropNodes(subsetA);
|
|
1514
|
-
const rootPropNode: EntityPropNode = {
|
|
1515
|
-
nodeType: "object",
|
|
1516
|
-
children: propNodes,
|
|
1517
|
-
};
|
|
1518
|
-
|
|
1519
|
-
const columnsZodType = (await this.propNodeToZodType(
|
|
1520
|
-
rootPropNode
|
|
1521
|
-
)) as z.ZodObject<any>;
|
|
1522
|
-
|
|
1523
|
-
const columnsNode = this.zodTypeToRenderingNode(columnsZodType);
|
|
1524
|
-
columnsNode.children = columnsNode.children!.map((child) => {
|
|
1525
|
-
if (child.renderType === "object") {
|
|
1526
|
-
const pickedCol = child.children!.find((cc) =>
|
|
1527
|
-
["title", "name"].includes(cc.name)
|
|
1528
|
-
);
|
|
1529
|
-
if (pickedCol) {
|
|
1530
|
-
return {
|
|
1531
|
-
...child,
|
|
1532
|
-
renderType: "object-pick",
|
|
1533
|
-
config: {
|
|
1534
|
-
picked: pickedCol.name,
|
|
1535
|
-
},
|
|
1536
|
-
};
|
|
1537
|
-
} else {
|
|
1538
|
-
return child;
|
|
1539
|
-
}
|
|
1540
|
-
} else if (
|
|
1541
|
-
child.renderType === "array" &&
|
|
1542
|
-
child.element &&
|
|
1543
|
-
child.element.renderType === "object"
|
|
1544
|
-
) {
|
|
1545
|
-
const pickedCol = child.element!.children!.find((cc) =>
|
|
1546
|
-
["title", "name"].includes(cc.name)
|
|
1547
|
-
);
|
|
1548
|
-
if (pickedCol) {
|
|
1549
|
-
return {
|
|
1550
|
-
...child,
|
|
1551
|
-
element: {
|
|
1552
|
-
...child.element,
|
|
1553
|
-
renderType: "object-pick",
|
|
1554
|
-
config: {
|
|
1555
|
-
picked: pickedCol.name,
|
|
1556
|
-
},
|
|
1557
|
-
},
|
|
1558
|
-
};
|
|
1559
|
-
} else {
|
|
1560
|
-
return child;
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
return child;
|
|
1564
|
-
});
|
|
534
|
+
syncUI() {
|
|
535
|
+
const uiPort = Sonamu.config.ui?.port ?? 57000;
|
|
1565
536
|
|
|
1566
|
-
|
|
537
|
+
fetch(`http://127.0.0.1:${uiPort}/api/reload`, {
|
|
538
|
+
method: "GET",
|
|
539
|
+
}).catch((e) =>
|
|
540
|
+
console.log(chalk.dim(`Failed to reload Sonamu UI: ${e.message}`))
|
|
541
|
+
);
|
|
1567
542
|
}
|
|
1568
543
|
|
|
544
|
+
/**
|
|
545
|
+
* 하위호환용 프록시 메소드입니다.
|
|
546
|
+
*/
|
|
1569
547
|
async createEntity(
|
|
1570
548
|
form: Omit<TemplateOptions["entity"], "title"> & { title?: string }
|
|
1571
549
|
) {
|
|
1572
|
-
|
|
1573
|
-
throw new BadRequestException("entityId는 CamelCase 형식이어야 합니다.");
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
await this.generateTemplate("entity", form);
|
|
1577
|
-
|
|
1578
|
-
// reload entities
|
|
1579
|
-
await EntityManager.reload();
|
|
1580
|
-
|
|
1581
|
-
// syncFromWatcher에서 처리하므로 주석처리
|
|
1582
|
-
// this.actionGenerateSchemas();
|
|
1583
|
-
|
|
1584
|
-
// // generate schemas, types
|
|
1585
|
-
// await Promise.all([
|
|
1586
|
-
// ...(form.parentId === undefined
|
|
1587
|
-
// ? [
|
|
1588
|
-
// this.generateTemplate("init_types", {
|
|
1589
|
-
// entityId: form.entityId,
|
|
1590
|
-
// }),
|
|
1591
|
-
// ]
|
|
1592
|
-
// : []),
|
|
1593
|
-
// ]);
|
|
550
|
+
return await createEntity(form);
|
|
1594
551
|
}
|
|
1595
552
|
|
|
553
|
+
/**
|
|
554
|
+
* 하위호환용 프록시 메소드입니다.
|
|
555
|
+
*/
|
|
1596
556
|
async delEntity(entityId: string): Promise<{ delPaths: string[] }> {
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
const delPaths = (() => {
|
|
1600
|
-
if (entity.parentId) {
|
|
1601
|
-
return [
|
|
1602
|
-
`${Sonamu.apiRootPath}/src/application/${entity.names.parentFs}/${entity.names.fs}.entity.json`,
|
|
1603
|
-
];
|
|
1604
|
-
} else {
|
|
1605
|
-
return [
|
|
1606
|
-
`${Sonamu.apiRootPath}/src/application/${entity.names.fs}`,
|
|
1607
|
-
`${Sonamu.apiRootPath}/dist/application/${entity.names.fs}`,
|
|
1608
|
-
...Sonamu.config.sync.targets
|
|
1609
|
-
.map((target) => [
|
|
1610
|
-
`${Sonamu.appRootPath}/${target}/src/services/${entity.names.fs}`,
|
|
1611
|
-
])
|
|
1612
|
-
.flat(),
|
|
1613
|
-
];
|
|
1614
|
-
}
|
|
1615
|
-
})(); // iife
|
|
1616
|
-
|
|
1617
|
-
for await (const delPath of delPaths) {
|
|
1618
|
-
if (await exists(delPath)) {
|
|
1619
|
-
console.log(chalk.red(`DELETE ${delPath}`));
|
|
1620
|
-
await rm(delPath, { recursive: true, force: true });
|
|
1621
|
-
} else {
|
|
1622
|
-
console.log(chalk.yellow(`NOT_EXISTS ${delPath}`));
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// reload entities
|
|
1627
|
-
await EntityManager.reload();
|
|
557
|
+
return await delEntity(entityId);
|
|
558
|
+
}
|
|
1628
559
|
|
|
1629
|
-
|
|
560
|
+
/**
|
|
561
|
+
* 하위호환용 프록시 메소드입니다.
|
|
562
|
+
*/
|
|
563
|
+
async generateTemplate(
|
|
564
|
+
key: TemplateKey,
|
|
565
|
+
templateOptions: any,
|
|
566
|
+
_generateOptions?: GenerateOptions
|
|
567
|
+
) {
|
|
568
|
+
return await generateTemplate(key, templateOptions, _generateOptions);
|
|
1630
569
|
}
|
|
1631
570
|
|
|
1632
|
-
|
|
1633
|
-
|
|
571
|
+
/**
|
|
572
|
+
* 하위호환용 프록시 메소드입니다.
|
|
573
|
+
*/
|
|
574
|
+
async renderTemplate(key: TemplateKey, templateOptions: any) {
|
|
575
|
+
return await renderTemplate(key, templateOptions);
|
|
576
|
+
}
|
|
1634
577
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
);
|
|
578
|
+
/**
|
|
579
|
+
* 하위호환용 프록시 메소드입니다.
|
|
580
|
+
*/
|
|
581
|
+
async renewChecksums(): Promise<void> {
|
|
582
|
+
return await renewChecksums();
|
|
1640
583
|
}
|
|
1641
584
|
}
|