sonamu 0.9.19 → 0.10.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.
Files changed (282) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +36 -0
  2. package/dist/ai/agents/agent.js +5 -7
  3. package/dist/ai/agents/index.js +1 -2
  4. package/dist/ai/agents/types.js +1 -1
  5. package/dist/ai/index.js +1 -2
  6. package/dist/ai/providers/rtzr/api.js +2 -3
  7. package/dist/ai/providers/rtzr/error.js +14 -29
  8. package/dist/ai/providers/rtzr/index.js +1 -2
  9. package/dist/ai/providers/rtzr/model.js +13 -20
  10. package/dist/ai/providers/rtzr/options.js +2 -3
  11. package/dist/ai/providers/rtzr/provider.js +2 -3
  12. package/dist/ai/providers/rtzr/utils.js +12 -21
  13. package/dist/api/base-frame.js +4 -4
  14. package/dist/api/caster.js +21 -38
  15. package/dist/api/code-converters.js +41 -98
  16. package/dist/api/config.d.ts +1 -10
  17. package/dist/api/config.d.ts.map +1 -1
  18. package/dist/api/config.js +9 -8
  19. package/dist/api/context.js +2 -3
  20. package/dist/api/decorators.js +80 -116
  21. package/dist/api/index.js +2 -3
  22. package/dist/api/secret.js +6 -10
  23. package/dist/api/sonamu.d.ts.map +1 -1
  24. package/dist/api/sonamu.js +200 -387
  25. package/dist/api/validator.js +5 -8
  26. package/dist/api/websocket-helpers.js +21 -32
  27. package/dist/auth/audit-log/builders.js +2 -3
  28. package/dist/auth/audit-log/events.js +2 -2
  29. package/dist/auth/audit-log/plugin.js +30 -61
  30. package/dist/auth/audit-log-ingestor.js +19 -41
  31. package/dist/auth/auth-generator.js +16 -41
  32. package/dist/auth/better-auth-entities.js +3 -4
  33. package/dist/auth/index.js +2 -3
  34. package/dist/auth/knex-adapter.js +18 -45
  35. package/dist/auth/plugins/entity-definitions/admin.js +2 -2
  36. package/dist/auth/plugins/entity-definitions/anonymous.js +2 -2
  37. package/dist/auth/plugins/entity-definitions/api-key.js +2 -2
  38. package/dist/auth/plugins/entity-definitions/audit-log.js +2 -2
  39. package/dist/auth/plugins/entity-definitions/index.js +2 -3
  40. package/dist/auth/plugins/entity-definitions/jwt.js +2 -2
  41. package/dist/auth/plugins/entity-definitions/organization.js +2 -2
  42. package/dist/auth/plugins/entity-definitions/passkey.js +2 -2
  43. package/dist/auth/plugins/entity-definitions/phone-number.js +2 -2
  44. package/dist/auth/plugins/entity-definitions/sso.js +2 -2
  45. package/dist/auth/plugins/entity-definitions/two-factor.js +2 -2
  46. package/dist/auth/plugins/entity-definitions/types.js +1 -1
  47. package/dist/auth/plugins/entity-definitions/username.js +2 -2
  48. package/dist/auth/plugins/index.js +1 -2
  49. package/dist/auth/plugins/wrappers/admin.js +2 -3
  50. package/dist/auth/plugins/wrappers/anonymous.js +2 -3
  51. package/dist/auth/plugins/wrappers/api-key.js +2 -3
  52. package/dist/auth/plugins/wrappers/index.js +1 -2
  53. package/dist/auth/plugins/wrappers/jwt.js +2 -3
  54. package/dist/auth/plugins/wrappers/organization.js +2 -3
  55. package/dist/auth/plugins/wrappers/passkey.js +2 -3
  56. package/dist/auth/plugins/wrappers/phone-number.js +2 -3
  57. package/dist/auth/plugins/wrappers/sso.js +2 -3
  58. package/dist/auth/plugins/wrappers/two-factor.js +2 -3
  59. package/dist/auth/plugins/wrappers/username.js +2 -3
  60. package/dist/bin/build-config.js +2 -2
  61. package/dist/bin/cli.js +151 -258
  62. package/dist/bin/fixture.d.ts.map +1 -1
  63. package/dist/bin/fixture.js +55 -97
  64. package/dist/bin/hmr-hook-register.js +3 -3
  65. package/dist/bin/migrate-targets.d.ts +3 -0
  66. package/dist/bin/migrate-targets.d.ts.map +1 -0
  67. package/dist/bin/migrate-targets.js +11 -0
  68. package/dist/bin/test-command.js +25 -55
  69. package/dist/bin/ts-loader-register.js +5 -6
  70. package/dist/bin/ts-loader-registration.js +6 -13
  71. package/dist/cache/cache-manager.js +3 -4
  72. package/dist/cache/decorator.js +11 -21
  73. package/dist/cache/drivers.js +2 -3
  74. package/dist/cache/index.js +2 -3
  75. package/dist/cache/types.js +1 -1
  76. package/dist/cache-control/cache-control.js +21 -34
  77. package/dist/cache-control/types.js +1 -1
  78. package/dist/compress/compress.js +10 -10
  79. package/dist/compress/index.js +1 -2
  80. package/dist/compress/types.js +1 -1
  81. package/dist/cone/cone-generator.js +25 -63
  82. package/dist/database/_batch_update.js +26 -46
  83. package/dist/database/base-model.js +44 -97
  84. package/dist/database/base-model.types.js +1 -1
  85. package/dist/database/db.d.ts +8 -14
  86. package/dist/database/db.d.ts.map +1 -1
  87. package/dist/database/db.js +127 -72
  88. package/dist/database/knex.js +5 -8
  89. package/dist/database/puri-subset.types.js +1 -1
  90. package/dist/database/puri-wrapper.js +11 -15
  91. package/dist/database/puri.js +117 -234
  92. package/dist/database/puri.types.js +3 -4
  93. package/dist/database/transaction-context.js +4 -5
  94. package/dist/database/upsert-builder.js +109 -176
  95. package/dist/dict/en.d.ts +1 -0
  96. package/dist/dict/en.d.ts.map +1 -1
  97. package/dist/dict/en.js +4 -4
  98. package/dist/dict/index.js +2 -3
  99. package/dist/dict/ko.d.ts +1 -0
  100. package/dist/dict/ko.d.ts.map +1 -1
  101. package/dist/dict/ko.js +4 -4
  102. package/dist/dict/rc-keys.js +3 -4
  103. package/dist/dict/sd.js +8 -19
  104. package/dist/dict/sonamu-dictionary.js +141 -284
  105. package/dist/dict/types.js +1 -1
  106. package/dist/dict/utils.js +4 -5
  107. package/dist/entity/entity-manager.d.ts +2 -2
  108. package/dist/entity/entity-manager.js +34 -82
  109. package/dist/entity/entity-template-cone.js +33 -66
  110. package/dist/entity/entity.js +156 -310
  111. package/dist/env.d.ts +14 -0
  112. package/dist/env.d.ts.map +1 -0
  113. package/dist/env.js +75 -0
  114. package/dist/exceptions/error-handler.js +2 -3
  115. package/dist/exceptions/so-exceptions.js +7 -5
  116. package/dist/filter/index.js +1 -2
  117. package/dist/filter/types.js +3 -4
  118. package/dist/filter/utils.js +21 -54
  119. package/dist/index.js +8 -7
  120. package/dist/logger/category.js +6 -12
  121. package/dist/logger/configure.js +23 -34
  122. package/dist/migration/code-generation.js +146 -314
  123. package/dist/migration/index-where-predicate.js +52 -144
  124. package/dist/migration/migration-set.js +19 -33
  125. package/dist/migration/migrator.d.ts +2 -0
  126. package/dist/migration/migrator.d.ts.map +1 -1
  127. package/dist/migration/migrator.js +69 -53
  128. package/dist/migration/postgresql-schema-reader.js +126 -225
  129. package/dist/migration/slack-confirm.d.ts +1 -0
  130. package/dist/migration/slack-confirm.d.ts.map +1 -1
  131. package/dist/migration/slack-confirm.js +28 -38
  132. package/dist/migration/types.js +1 -1
  133. package/dist/naite/messaging-types.js +1 -1
  134. package/dist/naite/naite-reporter.js +15 -32
  135. package/dist/naite/naite.js +43 -76
  136. package/dist/ssr/index.js +6 -9
  137. package/dist/ssr/registry.js +10 -18
  138. package/dist/ssr/renderer.js +10 -21
  139. package/dist/ssr/types.js +1 -1
  140. package/dist/storage/base-file.js +5 -10
  141. package/dist/storage/buffered-file.js +3 -4
  142. package/dist/storage/drivers.js +2 -3
  143. package/dist/storage/index.js +2 -3
  144. package/dist/storage/s3-driver.js +5 -9
  145. package/dist/storage/storage-manager.js +5 -5
  146. package/dist/storage/types.js +1 -1
  147. package/dist/storage/uploaded-file.js +4 -6
  148. package/dist/stream/index.js +1 -2
  149. package/dist/stream/sse.js +8 -13
  150. package/dist/stream/ws-audience-resolver.js +5 -5
  151. package/dist/stream/ws-audience.js +3 -4
  152. package/dist/stream/ws-cluster-bus.js +3 -4
  153. package/dist/stream/ws-core.js +1 -1
  154. package/dist/stream/ws-delivery.js +11 -25
  155. package/dist/stream/ws-local-connection-store.js +9 -18
  156. package/dist/stream/ws-presence-store.js +43 -97
  157. package/dist/stream/ws-registry.js +17 -22
  158. package/dist/stream/ws-telemetry-memory.js +38 -45
  159. package/dist/stream/ws-telemetry-trace.js +4 -6
  160. package/dist/stream/ws-telemetry.js +82 -135
  161. package/dist/stream/ws.js +47 -91
  162. package/dist/syncer/api-parser.js +81 -147
  163. package/dist/syncer/checksum.js +9 -20
  164. package/dist/syncer/code-generator.js +29 -47
  165. package/dist/syncer/entity-operations.js +17 -27
  166. package/dist/syncer/event-batcher.js +8 -15
  167. package/dist/syncer/file-patterns.js +3 -4
  168. package/dist/syncer/file-tracking.js +6 -10
  169. package/dist/syncer/index.js +1 -2
  170. package/dist/syncer/module-loader.js +10 -26
  171. package/dist/syncer/syncer-actions.js +19 -37
  172. package/dist/syncer/syncer.js +46 -98
  173. package/dist/syncer/watcher.js +12 -26
  174. package/dist/tasks/decorator.js +7 -11
  175. package/dist/tasks/step-wrapper.js +7 -8
  176. package/dist/tasks/workflow-manager.js +18 -25
  177. package/dist/template/entity-converter.js +40 -64
  178. package/dist/template/helpers.js +32 -63
  179. package/dist/template/implementations/entity.template.js +7 -11
  180. package/dist/template/implementations/entry-server.template.js +2 -3
  181. package/dist/template/implementations/generated.template.js +25 -51
  182. package/dist/template/implementations/generated_http.template.js +31 -58
  183. package/dist/template/implementations/generated_sso.template.js +45 -85
  184. package/dist/template/implementations/init_types.template.js +4 -7
  185. package/dist/template/implementations/model.template.js +5 -10
  186. package/dist/template/implementations/model_test.template.js +2 -3
  187. package/dist/template/implementations/queries.template.js +4 -7
  188. package/dist/template/implementations/sd.template.js +17 -35
  189. package/dist/template/implementations/services.template.js +18 -30
  190. package/dist/template/implementations/view_form.template.js +72 -125
  191. package/dist/template/implementations/view_id_all_select.template.js +2 -3
  192. package/dist/template/implementations/view_list.template.js +86 -143
  193. package/dist/template/implementations/view_search_input.template.js +2 -3
  194. package/dist/template/index.js +5 -8
  195. package/dist/template/template-manager.js +13 -26
  196. package/dist/template/template-types.js +2 -3
  197. package/dist/template/template.js +7 -11
  198. package/dist/template/zod-converter.js +173 -348
  199. package/dist/testing/_relation-graph.js +18 -37
  200. package/dist/testing/bootstrap.js +5 -8
  201. package/dist/testing/data-explorer.js +34 -78
  202. package/dist/testing/dev-test-routes.js +54 -60
  203. package/dist/testing/dev-vitest-manager.js +33 -84
  204. package/dist/testing/faker-mappings.js +3 -4
  205. package/dist/testing/fixture-generator.d.ts +2 -1
  206. package/dist/testing/fixture-generator.d.ts.map +1 -1
  207. package/dist/testing/fixture-generator.js +159 -321
  208. package/dist/testing/fixture-loader.js +2 -2
  209. package/dist/testing/fixture-manager.d.ts.map +1 -1
  210. package/dist/testing/fixture-manager.js +124 -227
  211. package/dist/testing/global-setup.d.ts.map +1 -1
  212. package/dist/testing/global-setup.js +29 -17
  213. package/dist/testing/index.js +1 -2
  214. package/dist/testing/naite-vitest-reporter.js +2 -3
  215. package/dist/testing/parallel-db-manager.js +5 -3
  216. package/dist/testing/vitest-helpers.d.ts.map +1 -1
  217. package/dist/testing/vitest-helpers.js +15 -12
  218. package/dist/types/types.d.ts +14 -14
  219. package/dist/types/types.js +27 -50
  220. package/dist/ui/ai-api.js +6 -11
  221. package/dist/ui/ai-client.js +86 -134
  222. package/dist/ui/api.js +99 -195
  223. package/dist/ui/cdd-service.js +78 -130
  224. package/dist/ui/cdd-types.js +1 -1
  225. package/dist/ui-web/assets/{index-Df8q-fhb.js → index-DFStGyd0.js} +49 -49
  226. package/dist/ui-web/assets/index-Dx4ap5i4.css +1 -0
  227. package/dist/ui-web/index.html +2 -2
  228. package/dist/utils/async-utils.js +13 -25
  229. package/dist/utils/class-name.js +3 -4
  230. package/dist/utils/console-util.js +11 -26
  231. package/dist/utils/controller.d.ts.map +1 -1
  232. package/dist/utils/controller.js +14 -12
  233. package/dist/utils/esm-utils.js +5 -8
  234. package/dist/utils/formatter.js +10 -22
  235. package/dist/utils/fs-utils.js +14 -25
  236. package/dist/utils/lodash-able.js +3 -4
  237. package/dist/utils/model.js +7 -14
  238. package/dist/utils/object-utils.js +41 -73
  239. package/dist/utils/path-utils.js +5 -9
  240. package/dist/utils/process-utils.js +4 -7
  241. package/dist/utils/sql-parser.js +6 -13
  242. package/dist/utils/type-utils.js +16 -26
  243. package/dist/utils/utils.js +18 -40
  244. package/dist/utils/zod-error.js +9 -16
  245. package/dist/vector/chunking.js +24 -37
  246. package/dist/vector/config.js +2 -2
  247. package/dist/vector/embedding.js +8 -19
  248. package/dist/vector/index.js +1 -2
  249. package/dist/vector/types.js +1 -1
  250. package/package.json +7 -7
  251. package/src/__tests__/env.test.ts +127 -0
  252. package/src/api/__tests__/config.test.ts +10 -1
  253. package/src/api/config.ts +4 -12
  254. package/src/api/sonamu.ts +14 -4
  255. package/src/bin/__tests__/migrate-targets.test.ts +28 -0
  256. package/src/bin/__tests__/test-command.test.ts +82 -1
  257. package/src/bin/cli.ts +9 -18
  258. package/src/bin/fixture.ts +5 -4
  259. package/src/bin/migrate-targets.ts +7 -0
  260. package/src/bin/test-command.ts +2 -2
  261. package/src/database/__tests__/db.test.ts +175 -0
  262. package/src/database/db.ts +193 -71
  263. package/src/dict/en.ts +2 -0
  264. package/src/dict/ko.ts +2 -0
  265. package/src/env.ts +123 -0
  266. package/src/migration/__tests__/migrator.test.ts +149 -0
  267. package/src/migration/migrator.ts +74 -17
  268. package/src/migration/slack-confirm.ts +21 -0
  269. package/src/skills/sonamu/database.md +1 -1
  270. package/src/skills/sonamu/entity-basic.md +31 -0
  271. package/src/skills/sonamu/puri.md +22 -0
  272. package/src/skills/sonamu/testing-devrunner.md +1 -1
  273. package/src/skills/sonamu/upsert.md +53 -6
  274. package/src/stream/ws-telemetry-memory.ts +2 -2
  275. package/src/testing/fixture-generator.ts +2 -1
  276. package/src/testing/fixture-manager.ts +3 -4
  277. package/src/testing/global-setup.ts +42 -18
  278. package/src/testing/vitest-helpers.ts +14 -0
  279. package/src/utils/controller.ts +14 -7
  280. package/tsdown.api.config.ts +6 -0
  281. package/dist/_virtual/rolldown_runtime.js +0 -39
  282. package/dist/ui-web/assets/index-D4rYm-Xz.css +0 -1
@@ -1,7 +1,6 @@
1
1
  import { Sonamu, init_sonamu } from "../api/sonamu.js";
2
2
  import { DEFAULT_VECTOR_CONFIG } from "./config.js";
3
3
  import { embedMany } from "ai";
4
-
5
4
  //#region src/vector/embedding.ts
6
5
  init_sonamu();
7
6
  /**
@@ -40,9 +39,7 @@ var EmbeddingClass = class {
40
39
  async getVoyageClient() {
41
40
  const { VoyageAIClient } = await import("voyageai");
42
41
  const apiKey = Sonamu.secrets?.voyage_api_key ?? process.env.VOYAGE_API_KEY;
43
- if (!apiKey) {
44
- throw new Error("VOYAGE_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
45
- }
42
+ if (!apiKey) throw new Error("VOYAGE_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
46
43
  return new VoyageAIClient({ apiKey });
47
44
  }
48
45
  /**
@@ -51,9 +48,7 @@ var EmbeddingClass = class {
51
48
  async getOpenAIProvider() {
52
49
  const { createOpenAI } = await import("@ai-sdk/openai");
53
50
  const apiKey = Sonamu.secrets?.openai_api_key ?? process.env.OPENAI_API_KEY;
54
- if (!apiKey) {
55
- throw new Error("OPENAI_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
56
- }
51
+ if (!apiKey) throw new Error("OPENAI_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
57
52
  return createOpenAI({ apiKey });
58
53
  }
59
54
  /**
@@ -65,9 +60,7 @@ var EmbeddingClass = class {
65
60
  */
66
61
  async embed(texts, provider, inputType = "document", onProgress) {
67
62
  const maxBatchSize = provider === "voyage" ? this.config.voyage.batchSize : this.config.openai.batchSize;
68
- if (texts.length <= maxBatchSize) {
69
- return provider === "voyage" ? await this.embedVoyage(texts, inputType) : await this.embedOpenAI(texts);
70
- }
63
+ if (texts.length <= maxBatchSize) return provider === "voyage" ? await this.embedVoyage(texts, inputType) : await this.embedOpenAI(texts);
71
64
  const batches = Array.from({ length: Math.ceil(texts.length / maxBatchSize) }, (_, i) => texts.slice(i * maxBatchSize, (i + 1) * maxBatchSize));
72
65
  const results = await Promise.all(batches.map((batch) => provider === "voyage" ? this.embedVoyage(batch, inputType) : this.embedOpenAI(batch)));
73
66
  onProgress?.(texts.length, texts.length);
@@ -77,8 +70,7 @@ var EmbeddingClass = class {
77
70
  * 단일 텍스트 임베딩 (편의 메서드)
78
71
  */
79
72
  async embedOne(text, provider, inputType = "document") {
80
- const results = await this.embed([text], provider, inputType);
81
- return results[0];
73
+ return (await this.embed([text], provider, inputType))[0];
82
74
  }
83
75
  /**
84
76
  * Voyage AI 임베딩
@@ -91,9 +83,7 @@ var EmbeddingClass = class {
91
83
  model: voyageConfig.model,
92
84
  inputType
93
85
  });
94
- if (!response.data) {
95
- throw new Error("Voyage API: 응답 데이터가 없습니다.");
96
- }
86
+ if (!response.data) throw new Error("Voyage API: 응답 데이터가 없습니다.");
97
87
  return response.data.map((item) => ({
98
88
  embedding: item.embedding ?? [],
99
89
  model: voyageConfig.model,
@@ -106,9 +96,8 @@ var EmbeddingClass = class {
106
96
  async embedOpenAI(texts) {
107
97
  const openai = await this.getOpenAIProvider();
108
98
  const openaiConfig = this.config.openai;
109
- const model = openai.embeddingModel(openaiConfig.model);
110
99
  const { embeddings, usage } = await embedMany({
111
- model,
100
+ model: openai.embeddingModel(openaiConfig.model),
112
101
  values: texts
113
102
  });
114
103
  return embeddings.map((embedding) => ({
@@ -125,7 +114,7 @@ var EmbeddingClass = class {
125
114
  }
126
115
  };
127
116
  const Embedding = new EmbeddingClass();
128
-
129
117
  //#endregion
130
118
  export { Embedding };
131
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1iZWRkaW5nLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92ZWN0b3IvZW1iZWRkaW5nLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHR5cGUgT3BlbkFJUHJvdmlkZXIgfSBmcm9tIFwiQGFpLXNkay9vcGVuYWlcIjtcbmltcG9ydCB7IGVtYmVkTWFueSB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBFbWJlZGRpbmdNb2RlbCB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBWb3lhZ2VBSUNsaWVudCB9IGZyb20gXCJ2b3lhZ2VhaVwiO1xuXG5pbXBvcnQgeyBTb25hbXUgfSBmcm9tIFwiLi4vYXBpL3NvbmFtdVwiO1xuaW1wb3J0IHsgREVGQVVMVF9WRUNUT1JfQ09ORklHIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQge1xuICB0eXBlIEVtYmVkZGluZ1Byb3ZpZGVyLFxuICB0eXBlIEVtYmVkZGluZ1Jlc3VsdCxcbiAgdHlwZSBQcm9ncmVzc0NhbGxiYWNrLFxuICB0eXBlIFZlY3RvckNvbmZpZyxcbiAgdHlwZSBWZWN0b3JJbnB1dFR5cGUsXG59IGZyb20gXCIuL3R5cGVzXCI7XG5cbi8qKlxuICog7J6E67Kg65SpIO2BtOudvOydtOyWuO2KuFxuICogVm95YWdlIEFJ7JmAIE9wZW5BSSDsnoTrsqDrlKnsnYQgU0RLIOuwqeyLneycvOuhnCDthrXtlakg7KeA7JuQXG4gKi9cbmNsYXNzIEVtYmVkZGluZ0NsYXNzIHtcbiAgcHJpdmF0ZSBjb25maWc6IFZlY3RvckNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IFBhcnRpYWw8VmVjdG9yQ29uZmlnPiA9IHt9KSB7XG4gICAgdGhpcy5jb25maWcgPSB7XG4gICAgICB2b3lhZ2U6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnZveWFnZSwgLi4uY29uZmlnLnZveWFnZSB9LFxuICAgICAgb3BlbmFpOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5vcGVuYWksIC4uLmNvbmZpZy5vcGVuYWkgfSxcbiAgICAgIGNodW5raW5nOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5jaHVua2luZywgLi4uY29uZmlnLmNodW5raW5nIH0sXG4gICAgICBzZWFyY2g6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnNlYXJjaCwgLi4uY29uZmlnLnNlYXJjaCB9LFxuICAgICAgcGd2ZWN0b3I6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnBndmVjdG9yLCAuLi5jb25maWcucGd2ZWN0b3IgfSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZveWFnZSBBSSDtgbTrnbzsnbTslrjtirgg7LSI6riw7ZmUXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldFZveWFnZUNsaWVudCgpOiBQcm9taXNlPFZveWFnZUFJQ2xpZW50PiB7XG4gICAgY29uc3QgeyBWb3lhZ2VBSUNsaWVudCB9ID0gYXdhaXQgaW1wb3J0KFwidm95YWdlYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/LnZveWFnZV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52LlZPWUFHRV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJWT1lBR0VfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBWb3lhZ2VBSUNsaWVudCh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuQUkgcHJvdmlkZXIg7IOd7ISxXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldE9wZW5BSVByb3ZpZGVyKCk6IFByb21pc2U8T3BlbkFJUHJvdmlkZXI+IHtcbiAgICBjb25zdCB7IGNyZWF0ZU9wZW5BSSB9ID0gYXdhaXQgaW1wb3J0KFwiQGFpLXNkay9vcGVuYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/Lm9wZW5haV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJPUEVOQUlfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIGNyZWF0ZU9wZW5BSSh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiDthY3siqTtirgg7J6E67Kg65SpIOyDneyEsVxuICAgKiBAcGFyYW0gdGV4dHMgLSDsnoTrsqDrlKntlaAg7YWN7Iqk7Yq4IOuwsOyXtCAoYmF0Y2hTaXpl7J207IOBIOyLnCDsnpDrj5kg67aE7ZWgKVxuICAgKiBAcGFyYW0gcHJvdmlkZXIgLSAndm95YWdlJyB8ICdvcGVuYWknXG4gICAqIEBwYXJhbSBpbnB1dFR5cGUgLSAnZG9jdW1lbnQnIHwgJ3F1ZXJ5JyAoVm95YWdlIEFJ66eMIO2VtOuLuSlcbiAgICogQHBhcmFtIG9uUHJvZ3Jlc3MgLSDsp4TtlonrpaAg7L2c67CxXG4gICAqL1xuICBhc3luYyBlbWJlZChcbiAgICB0ZXh0czogc3RyaW5nW10sXG4gICAgcHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyLFxuICAgIGlucHV0VHlwZTogVmVjdG9ySW5wdXRUeXBlID0gXCJkb2N1bWVudFwiLFxuICAgIG9uUHJvZ3Jlc3M/OiBQcm9ncmVzc0NhbGxiYWNrLFxuICApOiBQcm9taXNlPEVtYmVkZGluZ1Jlc3VsdFtdPiB7XG4gICAgY29uc3QgbWF4QmF0Y2hTaXplID1cbiAgICAgIHByb3ZpZGVyID09PSBcInZveWFnZVwiID8gdGhpcy5jb25maWcudm95YWdlLmJhdGNoU2l6ZSA6IHRoaXMuY29uZmlnLm9wZW5haS5iYXRjaFNpemU7XG5cbiAgICAvLyBiYXRjaFNpemXsnbTtlZjrqbQg67CU66GcIO2YuOy2nFxuICAgIGlmICh0ZXh0cy5sZW5ndGggPD0gbWF4QmF0Y2hTaXplKSB7XG4gICAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCJcbiAgICAgICAgPyBhd2FpdCB0aGlzLmVtYmVkVm95YWdlKHRleHRzLCBpbnB1dFR5cGUpXG4gICAgICAgIDogYXdhaXQgdGhpcy5lbWJlZE9wZW5BSSh0ZXh0cyk7XG4gICAgfVxuXG4gICAgLy8gYmF0Y2hTaXpl7J207IOB7J2066m0IOyekOuPmeycvOuhnCDrgpjriKDshJwg7LKY66asXG4gICAgY29uc3QgYmF0Y2hlcyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IE1hdGguY2VpbCh0ZXh0cy5sZW5ndGggLyBtYXhCYXRjaFNpemUpIH0sIChfLCBpKSA9PlxuICAgICAgdGV4dHMuc2xpY2UoaSAqIG1heEJhdGNoU2l6ZSwgKGkgKyAxKSAqIG1heEJhdGNoU2l6ZSksXG4gICAgKTtcblxuICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgIGJhdGNoZXMubWFwKChiYXRjaCkgPT5cbiAgICAgICAgcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmVtYmVkVm95YWdlKGJhdGNoLCBpbnB1dFR5cGUpIDogdGhpcy5lbWJlZE9wZW5BSShiYXRjaCksXG4gICAgICApLFxuICAgICk7XG5cbiAgICBvblByb2dyZXNzPy4odGV4dHMubGVuZ3RoLCB0ZXh0cy5sZW5ndGgpO1xuICAgIHJldHVybiByZXN1bHRzLmZsYXQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDri6jsnbwg7YWN7Iqk7Yq4IOyehOuyoOuUqSAo7Y647J2YIOuplOyEnOuTnClcbiAgICovXG4gIGFzeW5jIGVtYmVkT25lKFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgaW5wdXRUeXBlOiBWZWN0b3JJbnB1dFR5cGUgPSBcImRvY3VtZW50XCIsXG4gICk6IFByb21pc2U8RW1iZWRkaW5nUmVzdWx0PiB7XG4gICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IHRoaXMuZW1iZWQoW3RleHRdLCBwcm92aWRlciwgaW5wdXRUeXBlKTtcbiAgICByZXR1cm4gcmVzdWx0c1swXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWb3lhZ2UgQUkg7J6E67Kg65SpXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGVtYmVkVm95YWdlKFxuICAgIHRleHRzOiBzdHJpbmdbXSxcbiAgICBpbnB1dFR5cGU6IFZlY3RvcklucHV0VHlwZSxcbiAgKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IGF3YWl0IHRoaXMuZ2V0Vm95YWdlQ2xpZW50KCk7XG4gICAgY29uc3Qgdm95YWdlQ29uZmlnID0gdGhpcy5jb25maWcudm95YWdlO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuZW1iZWQoe1xuICAgICAgaW5wdXQ6IHRleHRzLFxuICAgICAgbW9kZWw6IHZveWFnZUNvbmZpZy5tb2RlbCxcbiAgICAgIGlucHV0VHlwZTogaW5wdXRUeXBlLFxuICAgIH0pO1xuICAgIGlmICghcmVzcG9uc2UuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVm95YWdlIEFQSTog7J2R64u1IOuNsOydtO2EsOqwgCDsl4bsirXri4jri6QuXCIpO1xuICAgIH1cblxuICAgIHJldHVybiByZXNwb25zZS5kYXRhLm1hcCgoaXRlbSkgPT4gKHtcbiAgICAgIGVtYmVkZGluZzogaXRlbS5lbWJlZGRpbmcgPz8gW10sXG4gICAgICBtb2RlbDogdm95YWdlQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogcmVzcG9uc2UudXNhZ2U/LnRvdGFsVG9rZW5zID8/IDAsXG4gICAgfSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW5BSSDsnoTrsqDrlKlcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW1iZWRPcGVuQUkodGV4dHM6IHN0cmluZ1tdKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IG9wZW5haSA9IGF3YWl0IHRoaXMuZ2V0T3BlbkFJUHJvdmlkZXIoKTtcbiAgICBjb25zdCBvcGVuYWlDb25maWcgPSB0aGlzLmNvbmZpZy5vcGVuYWk7XG4gICAgY29uc3QgbW9kZWwgPSBvcGVuYWkuZW1iZWRkaW5nTW9kZWwob3BlbmFpQ29uZmlnLm1vZGVsKTtcblxuICAgIGNvbnN0IHsgZW1iZWRkaW5ncywgdXNhZ2UgfSA9IGF3YWl0IGVtYmVkTWFueSh7XG4gICAgICBtb2RlbDogbW9kZWwgYXMgRW1iZWRkaW5nTW9kZWwsXG4gICAgICB2YWx1ZXM6IHRleHRzLFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGVtYmVkZGluZ3MubWFwKChlbWJlZGRpbmcpID0+ICh7XG4gICAgICBlbWJlZGRpbmcsXG4gICAgICBtb2RlbDogb3BlbmFpQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogdXNhZ2U/LnRva2VucyA/PyAwLFxuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDsnoTrsqDrlKkgcHJvdmlkZXLsnZgg7LCo7JuQIOyImCDrsJjtmZhcbiAgICovXG4gIGdldERpbWVuc2lvbnMocHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyKTogbnVtYmVyIHtcbiAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmNvbmZpZy52b3lhZ2UuZGltZW5zaW9ucyA6IHRoaXMuY29uZmlnLm9wZW5haS5kaW1lbnNpb25zO1xuICB9XG59XG5leHBvcnQgY29uc3QgRW1iZWRkaW5nID0gbmV3IEVtYmVkZGluZ0NsYXNzKCk7XG4iXSwibWFwcGluZ3MiOiI7Ozs7O2FBS3VDOzs7OztBQWN2QyxJQUFNLGlCQUFOLE1BQXFCO0NBQ25CLEFBQVE7Q0FFUixZQUFZLFNBQWdDLEVBQUUsRUFBRTtBQUM5QyxPQUFLLFNBQVM7R0FDWixRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87SUFBUTtHQUM3RCxRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFH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
119
+
120
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1iZWRkaW5nLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92ZWN0b3IvZW1iZWRkaW5nLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHR5cGUgT3BlbkFJUHJvdmlkZXIgfSBmcm9tIFwiQGFpLXNkay9vcGVuYWlcIjtcbmltcG9ydCB7IGVtYmVkTWFueSB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBFbWJlZGRpbmdNb2RlbCB9IGZyb20gXCJhaVwiO1xuaW1wb3J0IHsgdHlwZSBWb3lhZ2VBSUNsaWVudCB9IGZyb20gXCJ2b3lhZ2VhaVwiO1xuXG5pbXBvcnQgeyBTb25hbXUgfSBmcm9tIFwiLi4vYXBpL3NvbmFtdVwiO1xuaW1wb3J0IHsgREVGQVVMVF9WRUNUT1JfQ09ORklHIH0gZnJvbSBcIi4vY29uZmlnXCI7XG5pbXBvcnQge1xuICB0eXBlIEVtYmVkZGluZ1Byb3ZpZGVyLFxuICB0eXBlIEVtYmVkZGluZ1Jlc3VsdCxcbiAgdHlwZSBQcm9ncmVzc0NhbGxiYWNrLFxuICB0eXBlIFZlY3RvckNvbmZpZyxcbiAgdHlwZSBWZWN0b3JJbnB1dFR5cGUsXG59IGZyb20gXCIuL3R5cGVzXCI7XG5cbi8qKlxuICog7J6E67Kg65SpIO2BtOudvOydtOyWuO2KuFxuICogVm95YWdlIEFJ7JmAIE9wZW5BSSDsnoTrsqDrlKnsnYQgU0RLIOuwqeyLneycvOuhnCDthrXtlakg7KeA7JuQXG4gKi9cbmNsYXNzIEVtYmVkZGluZ0NsYXNzIHtcbiAgcHJpdmF0ZSBjb25maWc6IFZlY3RvckNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IFBhcnRpYWw8VmVjdG9yQ29uZmlnPiA9IHt9KSB7XG4gICAgdGhpcy5jb25maWcgPSB7XG4gICAgICB2b3lhZ2U6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnZveWFnZSwgLi4uY29uZmlnLnZveWFnZSB9LFxuICAgICAgb3BlbmFpOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5vcGVuYWksIC4uLmNvbmZpZy5vcGVuYWkgfSxcbiAgICAgIGNodW5raW5nOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5jaHVua2luZywgLi4uY29uZmlnLmNodW5raW5nIH0sXG4gICAgICBzZWFyY2g6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnNlYXJjaCwgLi4uY29uZmlnLnNlYXJjaCB9LFxuICAgICAgcGd2ZWN0b3I6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnBndmVjdG9yLCAuLi5jb25maWcucGd2ZWN0b3IgfSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZveWFnZSBBSSDtgbTrnbzsnbTslrjtirgg7LSI6riw7ZmUXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldFZveWFnZUNsaWVudCgpOiBQcm9taXNlPFZveWFnZUFJQ2xpZW50PiB7XG4gICAgY29uc3QgeyBWb3lhZ2VBSUNsaWVudCB9ID0gYXdhaXQgaW1wb3J0KFwidm95YWdlYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/LnZveWFnZV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52LlZPWUFHRV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJWT1lBR0VfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIG5ldyBWb3lhZ2VBSUNsaWVudCh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBPcGVuQUkgcHJvdmlkZXIg7IOd7ISxXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGdldE9wZW5BSVByb3ZpZGVyKCk6IFByb21pc2U8T3BlbkFJUHJvdmlkZXI+IHtcbiAgICBjb25zdCB7IGNyZWF0ZU9wZW5BSSB9ID0gYXdhaXQgaW1wb3J0KFwiQGFpLXNkay9vcGVuYWlcIik7XG4gICAgY29uc3QgYXBpS2V5ID0gU29uYW11LnNlY3JldHM/Lm9wZW5haV9hcGlfa2V5ID8/IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZO1xuICAgIGlmICghYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJPUEVOQUlfQVBJX0tFWeqwgCDshKTsoJXrkJjsp4Ag7JWK7JWY7Iq164uI64ukLiDtmZjqsr3rs4DsiJjrpbwg7ZmV7J247ZWY7IS47JqULlwiKTtcbiAgICB9XG4gICAgcmV0dXJuIGNyZWF0ZU9wZW5BSSh7IGFwaUtleSB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiDthY3siqTtirgg7J6E67Kg65SpIOyDneyEsVxuICAgKiBAcGFyYW0gdGV4dHMgLSDsnoTrsqDrlKntlaAg7YWN7Iqk7Yq4IOuwsOyXtCAoYmF0Y2hTaXpl7J207IOBIOyLnCDsnpDrj5kg67aE7ZWgKVxuICAgKiBAcGFyYW0gcHJvdmlkZXIgLSAndm95YWdlJyB8ICdvcGVuYWknXG4gICAqIEBwYXJhbSBpbnB1dFR5cGUgLSAnZG9jdW1lbnQnIHwgJ3F1ZXJ5JyAoVm95YWdlIEFJ66eMIO2VtOuLuSlcbiAgICogQHBhcmFtIG9uUHJvZ3Jlc3MgLSDsp4TtlonrpaAg7L2c67CxXG4gICAqL1xuICBhc3luYyBlbWJlZChcbiAgICB0ZXh0czogc3RyaW5nW10sXG4gICAgcHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyLFxuICAgIGlucHV0VHlwZTogVmVjdG9ySW5wdXRUeXBlID0gXCJkb2N1bWVudFwiLFxuICAgIG9uUHJvZ3Jlc3M/OiBQcm9ncmVzc0NhbGxiYWNrLFxuICApOiBQcm9taXNlPEVtYmVkZGluZ1Jlc3VsdFtdPiB7XG4gICAgY29uc3QgbWF4QmF0Y2hTaXplID1cbiAgICAgIHByb3ZpZGVyID09PSBcInZveWFnZVwiID8gdGhpcy5jb25maWcudm95YWdlLmJhdGNoU2l6ZSA6IHRoaXMuY29uZmlnLm9wZW5haS5iYXRjaFNpemU7XG5cbiAgICAvLyBiYXRjaFNpemXsnbTtlZjrqbQg67CU66GcIO2YuOy2nFxuICAgIGlmICh0ZXh0cy5sZW5ndGggPD0gbWF4QmF0Y2hTaXplKSB7XG4gICAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCJcbiAgICAgICAgPyBhd2FpdCB0aGlzLmVtYmVkVm95YWdlKHRleHRzLCBpbnB1dFR5cGUpXG4gICAgICAgIDogYXdhaXQgdGhpcy5lbWJlZE9wZW5BSSh0ZXh0cyk7XG4gICAgfVxuXG4gICAgLy8gYmF0Y2hTaXpl7J207IOB7J2066m0IOyekOuPmeycvOuhnCDrgpjriKDshJwg7LKY66asXG4gICAgY29uc3QgYmF0Y2hlcyA9IEFycmF5LmZyb20oeyBsZW5ndGg6IE1hdGguY2VpbCh0ZXh0cy5sZW5ndGggLyBtYXhCYXRjaFNpemUpIH0sIChfLCBpKSA9PlxuICAgICAgdGV4dHMuc2xpY2UoaSAqIG1heEJhdGNoU2l6ZSwgKGkgKyAxKSAqIG1heEJhdGNoU2l6ZSksXG4gICAgKTtcblxuICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgIGJhdGNoZXMubWFwKChiYXRjaCkgPT5cbiAgICAgICAgcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmVtYmVkVm95YWdlKGJhdGNoLCBpbnB1dFR5cGUpIDogdGhpcy5lbWJlZE9wZW5BSShiYXRjaCksXG4gICAgICApLFxuICAgICk7XG5cbiAgICBvblByb2dyZXNzPy4odGV4dHMubGVuZ3RoLCB0ZXh0cy5sZW5ndGgpO1xuICAgIHJldHVybiByZXN1bHRzLmZsYXQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDri6jsnbwg7YWN7Iqk7Yq4IOyehOuyoOuUqSAo7Y647J2YIOuplOyEnOuTnClcbiAgICovXG4gIGFzeW5jIGVtYmVkT25lKFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgaW5wdXRUeXBlOiBWZWN0b3JJbnB1dFR5cGUgPSBcImRvY3VtZW50XCIsXG4gICk6IFByb21pc2U8RW1iZWRkaW5nUmVzdWx0PiB7XG4gICAgY29uc3QgcmVzdWx0cyA9IGF3YWl0IHRoaXMuZW1iZWQoW3RleHRdLCBwcm92aWRlciwgaW5wdXRUeXBlKTtcbiAgICByZXR1cm4gcmVzdWx0c1swXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWb3lhZ2UgQUkg7J6E67Kg65SpXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGVtYmVkVm95YWdlKFxuICAgIHRleHRzOiBzdHJpbmdbXSxcbiAgICBpbnB1dFR5cGU6IFZlY3RvcklucHV0VHlwZSxcbiAgKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IGF3YWl0IHRoaXMuZ2V0Vm95YWdlQ2xpZW50KCk7XG4gICAgY29uc3Qgdm95YWdlQ29uZmlnID0gdGhpcy5jb25maWcudm95YWdlO1xuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuZW1iZWQoe1xuICAgICAgaW5wdXQ6IHRleHRzLFxuICAgICAgbW9kZWw6IHZveWFnZUNvbmZpZy5tb2RlbCxcbiAgICAgIGlucHV0VHlwZTogaW5wdXRUeXBlLFxuICAgIH0pO1xuICAgIGlmICghcmVzcG9uc2UuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVm95YWdlIEFQSTog7J2R64u1IOuNsOydtO2EsOqwgCDsl4bsirXri4jri6QuXCIpO1xuICAgIH1cblxuICAgIHJldHVybiByZXNwb25zZS5kYXRhLm1hcCgoaXRlbSkgPT4gKHtcbiAgICAgIGVtYmVkZGluZzogaXRlbS5lbWJlZGRpbmcgPz8gW10sXG4gICAgICBtb2RlbDogdm95YWdlQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogcmVzcG9uc2UudXNhZ2U/LnRvdGFsVG9rZW5zID8/IDAsXG4gICAgfSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW5BSSDsnoTrsqDrlKlcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW1iZWRPcGVuQUkodGV4dHM6IHN0cmluZ1tdKTogUHJvbWlzZTxFbWJlZGRpbmdSZXN1bHRbXT4ge1xuICAgIGNvbnN0IG9wZW5haSA9IGF3YWl0IHRoaXMuZ2V0T3BlbkFJUHJvdmlkZXIoKTtcbiAgICBjb25zdCBvcGVuYWlDb25maWcgPSB0aGlzLmNvbmZpZy5vcGVuYWk7XG4gICAgY29uc3QgbW9kZWwgPSBvcGVuYWkuZW1iZWRkaW5nTW9kZWwob3BlbmFpQ29uZmlnLm1vZGVsKTtcblxuICAgIGNvbnN0IHsgZW1iZWRkaW5ncywgdXNhZ2UgfSA9IGF3YWl0IGVtYmVkTWFueSh7XG4gICAgICBtb2RlbDogbW9kZWwgYXMgRW1iZWRkaW5nTW9kZWwsXG4gICAgICB2YWx1ZXM6IHRleHRzLFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGVtYmVkZGluZ3MubWFwKChlbWJlZGRpbmcpID0+ICh7XG4gICAgICBlbWJlZGRpbmcsXG4gICAgICBtb2RlbDogb3BlbmFpQ29uZmlnLm1vZGVsLFxuICAgICAgdG9rZW5Db3VudDogdXNhZ2U/LnRva2VucyA/PyAwLFxuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDsnoTrsqDrlKkgcHJvdmlkZXLsnZgg7LCo7JuQIOyImCDrsJjtmZhcbiAgICovXG4gIGdldERpbWVuc2lvbnMocHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyKTogbnVtYmVyIHtcbiAgICByZXR1cm4gcHJvdmlkZXIgPT09IFwidm95YWdlXCIgPyB0aGlzLmNvbmZpZy52b3lhZ2UuZGltZW5zaW9ucyA6IHRoaXMuY29uZmlnLm9wZW5haS5kaW1lbnNpb25zO1xuICB9XG59XG5leHBvcnQgY29uc3QgRW1iZWRkaW5nID0gbmV3IEVtYmVkZGluZ0NsYXNzKCk7XG4iXSwibWFwcGluZ3MiOiI7Ozs7WUFLc0M7Ozs7O0FBY3RDLElBQU0saUJBQU4sTUFBcUI7Q0FDbkI7Q0FFQSxZQUFZLFNBQWdDLENBQUMsR0FBRztFQUM5QyxLQUFLLFNBQVM7R0FDWixRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87R0FBTztHQUM1RCxRQUFRO0lBQUUsR0FBRyxzQkFBc0I7SUFBUSxHQUFHLE9BQU87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
@@ -2,5 +2,4 @@ import { DEFAULT_VECTOR_CONFIG, createVectorConfig } from "./config.js";
2
2
  import { Chunking } from "./chunking.js";
3
3
  import { Embedding } from "./embedding.js";
4
4
  import "./types.js";
5
-
6
- export { Chunking, DEFAULT_VECTOR_CONFIG, Embedding, createVectorConfig };
5
+ export { Chunking, DEFAULT_VECTOR_CONFIG, Embedding, createVectorConfig };
@@ -1 +1 @@
1
- export { };
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonamu",
3
- "version": "0.9.19",
3
+ "version": "0.10.0",
4
4
  "description": "Sonamu — TypeScript Fullstack API Framework",
5
5
  "keywords": [
6
6
  "framework",
@@ -120,18 +120,18 @@
120
120
  "minimatch": "^10.0.3",
121
121
  "node-cron": "^4.2.1",
122
122
  "node-sql-parser": "^5.2.0",
123
- "oxfmt": "^0.43.0",
124
- "oxlint": "^1.61.0",
123
+ "oxfmt": "^0.55.0",
124
+ "oxlint": "^1.70.0",
125
125
  "pg": "^8.16.3",
126
126
  "prompts": "^2.4.2",
127
127
  "qs": "^6.14.1",
128
128
  "radashi": "^12.2.0",
129
129
  "tsicli": "^1.0.5",
130
- "vite": "8.0.5",
131
- "vitest": "^4.1.2",
130
+ "vite": "8.0.16",
131
+ "vitest": "^4.1.9",
132
132
  "@sonamu-kit/hmr-runner": "^0.2.0",
133
+ "@sonamu-kit/tasks": "^0.3.1",
133
134
  "@sonamu-kit/ts-loader": "^2.2.0",
134
- "@sonamu-kit/tasks": "^0.3.0",
135
135
  "@sonamu-kit/hmr-hook": "^0.5.1"
136
136
  },
137
137
  "devDependencies": {
@@ -143,7 +143,7 @@
143
143
  "@types/prompts": "^2.0.14",
144
144
  "@types/qs": "^6.14.0",
145
145
  "nodemon": "^3.1.10",
146
- "tsdown": "^0.12.5",
146
+ "tsdown": "^0.22.3",
147
147
  "typescript": "^6.0.3"
148
148
  },
149
149
  "peerDependencies": {
@@ -0,0 +1,127 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+
7
+ import { readAllEnvironmentSnapshots, readEnvironmentSnapshot } from "../env";
8
+
9
+ describe("readAllEnvironmentSnapshots", () => {
10
+ const tempRoots: string[] = [];
11
+
12
+ afterEach(async () => {
13
+ await Promise.all(
14
+ tempRoots.splice(0).map((rootPath) => rm(rootPath, { recursive: true, force: true })),
15
+ );
16
+ });
17
+
18
+ it("does not reuse the current environment dotenv values as other environments' base values", async () => {
19
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
20
+ tempRoots.push(rootPath);
21
+
22
+ await mkdir(rootPath, { recursive: true });
23
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_USER=base_user\n");
24
+ await writeFile(
25
+ path.join(rootPath, ".env.development"),
26
+ "SONAMU_DB_HOST=development.example.com\nSONAMU_DB_PASSWORD=development_password\n",
27
+ );
28
+ await writeFile(path.join(rootPath, ".env.staging"), "SONAMU_DB_HOST=staging.example.com\n");
29
+
30
+ const snapshots = readAllEnvironmentSnapshots(rootPath, {
31
+ NODE_ENV: "development",
32
+ SONAMU_DB_PASSWORD: "shell_password",
33
+ });
34
+
35
+ expect(snapshots.development.SONAMU_DB_HOST).toBe("development.example.com");
36
+ expect(snapshots.development.SONAMU_DB_PASSWORD).toBe("shell_password");
37
+ expect(snapshots.staging.SONAMU_DB_HOST).toBe("staging.example.com");
38
+ expect(snapshots.staging.SONAMU_DB_PASSWORD).toBe("shell_password");
39
+ expect(snapshots.staging.SONAMU_DB_USER).toBe("base_user");
40
+ });
41
+
42
+ it("throws when both common and environment dotenv files are missing", async () => {
43
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
44
+ tempRoots.push(rootPath);
45
+
46
+ await writeFile(path.join(rootPath, ".env.local"), "SONAMU_DB_HOST=local.example.com\n");
47
+
48
+ expect(() => readAllEnvironmentSnapshots(rootPath)).toThrow(
49
+ /Missing Sonamu dotenv file.*\.env.*\.env\.test/,
50
+ );
51
+ });
52
+
53
+ it("allows environment snapshots when only the common dotenv file exists", async () => {
54
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
55
+ tempRoots.push(rootPath);
56
+
57
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=common.example.com\n");
58
+
59
+ const snapshots = readAllEnvironmentSnapshots(rootPath);
60
+
61
+ expect(snapshots.development.SONAMU_DB_HOST).toBe("common.example.com");
62
+ expect(snapshots.production.SONAMU_DB_HOST).toBe("common.example.com");
63
+ });
64
+
65
+ it("allows an environment snapshot when only the environment dotenv file exists", async () => {
66
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
67
+ tempRoots.push(rootPath);
68
+
69
+ await writeFile(path.join(rootPath, ".env.development"), "SONAMU_DB_HOST=dev.example.com\n");
70
+
71
+ const snapshot = readEnvironmentSnapshot(rootPath, "development");
72
+
73
+ expect(snapshot.SONAMU_DB_HOST).toBe("dev.example.com");
74
+ });
75
+
76
+ it("keeps exported environment variables over dotenv file values", async () => {
77
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
78
+ tempRoots.push(rootPath);
79
+
80
+ await writeFile(
81
+ path.join(rootPath, ".env.development"),
82
+ "SONAMU_DB_HOST=file-host\nSONAMU_DB_PASSWORD=file-password\n",
83
+ );
84
+
85
+ const snapshot = readEnvironmentSnapshot(rootPath, "development", {
86
+ SONAMU_DB_HOST: "runtime-host",
87
+ SONAMU_DB_PASSWORD: "runtime-password",
88
+ });
89
+
90
+ expect(snapshot.SONAMU_DB_HOST).toBe("runtime-host");
91
+ expect(snapshot.SONAMU_DB_PASSWORD).toBe("runtime-password");
92
+ expect(snapshot.NODE_ENV).toBe("development");
93
+ });
94
+
95
+ it("lets environment dotenv values override common dotenv values", async () => {
96
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
97
+ tempRoots.push(rootPath);
98
+
99
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=common.example.com\n");
100
+ await writeFile(path.join(rootPath, ".env.production"), "SONAMU_DB_HOST=prod.example.com\n");
101
+
102
+ const snapshot = readEnvironmentSnapshot(rootPath, "production");
103
+
104
+ expect(snapshot.SONAMU_DB_HOST).toBe("prod.example.com");
105
+ expect(snapshot.NODE_ENV).toBe("production");
106
+ });
107
+
108
+ it("does not let preloaded common dotenv values override environment dotenv values", async () => {
109
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-env-test-"));
110
+ tempRoots.push(rootPath);
111
+
112
+ await writeFile(
113
+ path.join(rootPath, ".env"),
114
+ "SONAMU_DB_HOST=common.example.com\nSONAMU_DB_USER=common_user\n",
115
+ );
116
+ await writeFile(path.join(rootPath, ".env.production"), "SONAMU_DB_HOST=prod.example.com\n");
117
+
118
+ const snapshot = readEnvironmentSnapshot(rootPath, "production", {
119
+ SONAMU_DB_HOST: "common.example.com",
120
+ SONAMU_DB_USER: "shell_user",
121
+ });
122
+
123
+ expect(snapshot.SONAMU_DB_HOST).toBe("prod.example.com");
124
+ expect(snapshot.SONAMU_DB_USER).toBe("shell_user");
125
+ expect(snapshot.NODE_ENV).toBe("production");
126
+ });
127
+ });
@@ -21,7 +21,9 @@ function resetRegisterState() {
21
21
  }
22
22
 
23
23
  async function createTempRoot(): Promise<string> {
24
- return mkdtemp(path.join(os.tmpdir(), "sonamu-config-test-"));
24
+ const rootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-config-test-"));
25
+ await writeFile(path.join(rootPath, ".env"), "SONAMU_DB_HOST=localhost\n");
26
+ return rootPath;
25
27
  }
26
28
 
27
29
  async function writeSourceFixture(rootPath: string): Promise<void> {
@@ -136,6 +138,7 @@ describe("loadConfig", () => {
136
138
  const tempRoots: string[] = [];
137
139
  const originalHot = process.env.HOT;
138
140
  const originalVitest = process.env.VITEST;
141
+ const originalNodeEnv = process.env.NODE_ENV;
139
142
 
140
143
  beforeEach(() => {
141
144
  vi.resetModules();
@@ -162,6 +165,12 @@ describe("loadConfig", () => {
162
165
  process.env.VITEST = originalVitest;
163
166
  }
164
167
 
168
+ if (originalNodeEnv === undefined) {
169
+ delete process.env.NODE_ENV;
170
+ } else {
171
+ process.env.NODE_ENV = originalNodeEnv;
172
+ }
173
+
165
174
  await Promise.all(
166
175
  tempRoots.splice(0).map((rootPath) => rm(rootPath, { recursive: true, force: true })),
167
176
  );
package/src/api/config.ts CHANGED
@@ -17,6 +17,7 @@ import { type Knex } from "knex";
17
17
 
18
18
  import { type CacheConfig } from "../cache/types";
19
19
  import { type SonamuDBConfig } from "../database/db";
20
+ import { applyCurrentSnapshotToProcessEnv } from "../env";
20
21
  import { type SonamuLoggingOptions } from "../logger/configure";
21
22
  import { type StorageConfig } from "../storage/types";
22
23
  import { type WebSocketRuntimeOptions } from "../stream/ws";
@@ -94,19 +95,8 @@ export type SonamuConfig<TSinkId extends string = string, TFilterId extends stri
94
95
  database: {
95
96
  // 데이터베이스(pg는 pg 모듈, pgnative는 pg-native 모듈의 설치가 필요합니다.)
96
97
  database?: "pg" | "pgnative";
97
- // 기본 데이터베이스 이름
98
- name: string;
99
98
  // 모든 환경에 적용될 기본 Knex 옵션
100
- defaultOptions: DatabaseConfig;
101
- // 환경별 설정
102
- environments?: {
103
- development?: DatabaseConfig;
104
- development_slave?: DatabaseConfig;
105
- production?: DatabaseConfig;
106
- production_slave?: DatabaseConfig;
107
- fixture?: DatabaseConfig;
108
- test?: DatabaseConfig;
109
- };
99
+ defaultOptions?: DatabaseConfig;
110
100
  };
111
101
 
112
102
  logging?: false | SonamuLoggingOptions<TSinkId, TFilterId>;
@@ -319,6 +309,8 @@ export function defineConfig(config: Executable<SonamuConfig>): Promise<SonamuCo
319
309
  * @returns
320
310
  */
321
311
  export async function loadConfig(rootPath: string): Promise<SonamuConfig> {
312
+ applyCurrentSnapshotToProcessEnv(rootPath);
313
+
322
314
  const shouldLoadSourceConfig = process.env.HOT === "yes" || process.env.VITEST === "true";
323
315
  const configPath = shouldLoadSourceConfig
324
316
  ? `${rootPath}/src/sonamu.config.ts`
package/src/api/sonamu.ts CHANGED
@@ -24,6 +24,7 @@ import { DB } from "../database/db";
24
24
  import { type SonamuDBConfig } from "../database/db";
25
25
  import { SD, setSDConfig } from "../dict/sd";
26
26
  import { type LocalizedString } from "../dict/types";
27
+ import { getSonamuEnvironment, readAllEnvironmentSnapshots } from "../env";
27
28
  import { NotFoundException } from "../exceptions/so-exceptions";
28
29
  import { BufferedFile } from "../storage/buffered-file";
29
30
  import { type StorageManager } from "../storage/storage-manager";
@@ -231,6 +232,7 @@ class SonamuClass {
231
232
  // API 루트 패스
232
233
  const { findApiRootPath } = await import("../utils/utils");
233
234
  this.apiRootPath = apiRootPath ?? findApiRootPath();
235
+ const baseEnvBeforeConfigLoad = { ...process.env };
234
236
 
235
237
  // 설정을 로딩하는 것부터 시작
236
238
  const configStart = performance.now();
@@ -240,6 +242,7 @@ class SonamuClass {
240
242
  setSDConfig(this.config.i18n);
241
243
  // sonamu.config.ts 기본값 설정
242
244
  this.config.database.database = this.config.database.database ?? "pg";
245
+ this.config.database.defaultOptions = this.config.database.defaultOptions ?? {};
243
246
  this.config.database.defaultOptions.client = this.config.database.database ?? "pg";
244
247
 
245
248
  // 로깅 설정
@@ -252,7 +255,15 @@ class SonamuClass {
252
255
 
253
256
  // DB 로드
254
257
  const { DB } = await import("../database/db");
255
- this.dbConfig = DB.generateDBConfig(this.config.database);
258
+ const { isLocal: isLocalEnvironment } = await import("../utils/controller");
259
+ const environmentSnapshots = isLocalEnvironment()
260
+ ? readAllEnvironmentSnapshots(this.apiRootPath, baseEnvBeforeConfigLoad)
261
+ : undefined;
262
+ this.dbConfig = DB.generateDBConfig(
263
+ this.config.database,
264
+ this.config.projectName,
265
+ environmentSnapshots,
266
+ );
256
267
  DB.setConfig(this.dbConfig);
257
268
 
258
269
  // Entity 로드
@@ -1825,8 +1836,7 @@ class SonamuClass {
1825
1836
 
1826
1837
  private async printStartupSummary() {
1827
1838
  const chalk = (await import("chalk")).default;
1828
- const env = process.env.NODE_ENV ?? "development";
1829
- const activePreset = env === "production" ? "production_master" : "development_master";
1839
+ const activePreset = getSonamuEnvironment();
1830
1840
 
1831
1841
  const dim = (msg: string) => console.log(chalk.dim(`✓ ${msg}`));
1832
1842
  const green = (msg: string) => console.log(chalk.green(`✓ ${msg}`));
@@ -1843,7 +1853,7 @@ class SonamuClass {
1843
1853
  | { host?: string; port?: number; database?: string }
1844
1854
  | undefined;
1845
1855
  const host = conn?.host ?? "localhost";
1846
- const addr = `@ ${host}:${conn?.port ?? 5432}/${conn?.database ?? this.config.database.name}`;
1856
+ const addr = `@ ${host}:${conn?.port ?? 5432}/${conn?.database ?? "(unknown)"}`;
1847
1857
  const padded = name.padEnd(maxLen);
1848
1858
  const remoteTag = isLocal() && !isLocalHost(host) ? chalk.yellow(` \u26a0 remote`) : "";
1849
1859
 
@@ -0,0 +1,28 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+
3
+ import { getMigrateRunTargets } from "../migrate-targets";
4
+
5
+ describe("getMigrateRunTargets", () => {
6
+ const originalEnv = { ...process.env };
7
+
8
+ afterEach(() => {
9
+ for (const key of Object.keys(process.env)) {
10
+ if (!(key in originalEnv)) {
11
+ delete process.env[key];
12
+ }
13
+ }
14
+ Object.assign(process.env, originalEnv);
15
+ });
16
+
17
+ it("includes fixture when running migrations in the test environment", () => {
18
+ process.env.NODE_ENV = "test";
19
+
20
+ expect(getMigrateRunTargets()).toEqual(["test", "fixture"]);
21
+ });
22
+
23
+ it("uses only the current environment outside test", () => {
24
+ process.env.NODE_ENV = "staging";
25
+
26
+ expect(getMigrateRunTargets()).toEqual(["staging"]);
27
+ });
28
+ });
@@ -1,4 +1,9 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const originalArgv = process.argv;
4
+ const originalNodeEnv = process.env.NODE_ENV;
5
+ const originalVitest = process.env.VITEST;
6
+ const originalFetch = globalThis.fetch;
2
7
 
3
8
  // process.argv 파싱 로직을 검증하는 유닛 테스트
4
9
  // test-command.ts의 파싱 로직을 동일하게 구현하여 독립 검증
@@ -83,3 +88,79 @@ describe("test-command argument parsing", () => {
83
88
  expect(showTraces).toBe(true);
84
89
  });
85
90
  });
91
+
92
+ describe("test-command dev server endpoint resolution", () => {
93
+ beforeEach(() => {
94
+ vi.resetModules();
95
+ vi.restoreAllMocks();
96
+ process.argv = [process.execPath, "sonamu", "test", "--status"];
97
+ process.env.NODE_ENV = "development";
98
+ delete process.env.VITEST;
99
+ });
100
+
101
+ afterEach(() => {
102
+ vi.resetModules();
103
+ vi.restoreAllMocks();
104
+ process.argv = originalArgv;
105
+ globalThis.fetch = originalFetch;
106
+
107
+ if (originalNodeEnv === undefined) {
108
+ delete process.env.NODE_ENV;
109
+ } else {
110
+ process.env.NODE_ENV = originalNodeEnv;
111
+ }
112
+
113
+ if (originalVitest === undefined) {
114
+ delete process.env.VITEST;
115
+ } else {
116
+ process.env.VITEST = originalVitest;
117
+ }
118
+ });
119
+
120
+ it("status 명령은 NODE_ENV=test로 바꾸지 않고 dev server 설정의 endpoint를 호출한다", async () => {
121
+ const loadConfig = vi.fn(async () => {
122
+ expect(process.env.NODE_ENV).toBe("development");
123
+ expect(process.env.VITEST).toBe("true");
124
+ return {
125
+ server: {
126
+ listen: {
127
+ host: "127.0.0.1",
128
+ port: 4401,
129
+ },
130
+ },
131
+ test: {
132
+ devRunner: {
133
+ enabled: true,
134
+ routePrefix: "/__dev_test__",
135
+ },
136
+ },
137
+ };
138
+ });
139
+ const fetch = vi.fn(async () => {
140
+ return new Response(
141
+ JSON.stringify({
142
+ ready: true,
143
+ running: false,
144
+ lastRunAt: null,
145
+ sseAvailable: true,
146
+ }),
147
+ {
148
+ status: 200,
149
+ headers: { "Content-Type": "application/json" },
150
+ },
151
+ );
152
+ });
153
+
154
+ vi.doMock("../../api/config", () => ({ loadConfig }));
155
+ vi.doMock("../../utils/utils", () => ({ findApiRootPath: () => "/tmp/api" }));
156
+ vi.spyOn(console, "log").mockImplementation(() => {});
157
+ globalThis.fetch = fetch as typeof globalThis.fetch;
158
+
159
+ const { testCommand } = await import("../test-command");
160
+ await testCommand();
161
+
162
+ expect(loadConfig).toHaveBeenCalledWith("/tmp/api");
163
+ expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:4401/__dev_test__/status");
164
+ expect(process.env.NODE_ENV).toBe("development");
165
+ });
166
+ });
package/src/bin/cli.ts CHANGED
@@ -1,8 +1,3 @@
1
- import chalk from "chalk";
2
- import dotenv from "dotenv";
3
-
4
- dotenv.config();
5
-
6
1
  import assert from "assert";
7
2
  import { execSync, spawn } from "child_process";
8
3
  import { cp, mkdir, readdir, readFile, rm, symlink, writeFile } from "fs/promises";
@@ -11,6 +6,7 @@ import os from "os";
11
6
  import path from "path";
12
7
  import process from "process";
13
8
 
9
+ import chalk from "chalk";
14
10
  import knex from "knex";
15
11
  import { type Knex } from "knex";
16
12
  import { tsicli } from "tsicli";
@@ -21,6 +17,7 @@ import { isValidPluginId, SUPPORTED_PLUGIN_IDS } from "../auth/plugins/entity-de
21
17
  import { type BetterAuthPluginId } from "../auth/plugins/entity-definitions";
22
18
  import { type SonamuDBConfig } from "../database/db";
23
19
  import { EntityManager } from "../entity/entity-manager";
20
+ import { getSonamuEnvironment } from "../env";
24
21
  import { Migrator } from "../migration/migrator";
25
22
  import { FixtureManager } from "../testing/fixture-manager";
26
23
  import {
@@ -36,6 +33,7 @@ import { findApiRootPath, findAppRootPath } from "../utils/utils";
36
33
  import { API_ARTIFACTS, WEB_ARTIFACTS } from "./build-config";
37
34
  import { type BuildArtifact } from "./build-config";
38
35
  import { fixtureExploreCommand, fixtureFetchCommand, fixtureGenCommand } from "./fixture";
36
+ import { getMigrateRunTargets } from "./migrate-targets";
39
37
  import { testCommand } from "./test-command";
40
38
 
41
39
  let migrator: Migrator;
@@ -144,8 +142,9 @@ async function bootstrap() {
144
142
  name: "#targets",
145
143
  message: "Please input #targets",
146
144
  choices: [
147
- { title: "Development", value: "development_master" },
148
- { title: "Production", value: "production_master" },
145
+ { title: "Development", value: "development" },
146
+ { title: "Staging", value: "staging" },
147
+ { title: "Production", value: "production" },
149
148
  { title: "Fixture", value: "fixture" },
150
149
  { title: "Test", value: "test" },
151
150
  ],
@@ -284,7 +283,7 @@ function spawnApiDevServer(options?: { extraEnv?: Record<string, string> }) {
284
283
  stdio: "inherit",
285
284
  env: {
286
285
  ...process.env,
287
- NODE_ENV: "development",
286
+ NODE_ENV: process.env.NODE_ENV ?? "development",
288
287
  HOT: "yes", // 얘가 있어야 HMR이 활성화됩니다.
289
288
  API_ROOT_PATH: apiRoot, // 이 경로가 hmr-hook의 루트 디렉토리가 됩니다.
290
289
  ...options?.extraEnv,
@@ -553,15 +552,7 @@ async function migrate_apply(targets: (keyof SonamuDBConfig)[]) {
553
552
 
554
553
  async function migrate_run() {
555
554
  await setupMigrator();
556
- const localHosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
557
- const targets = Object.keys(Sonamu.dbConfig).filter((target) => {
558
- const targetConfig = Sonamu.dbConfig[target as keyof SonamuDBConfig];
559
- const host = (targetConfig?.connection as { host?: string })?.host ?? "localhost";
560
- return localHosts.includes(host.toLowerCase());
561
- });
562
-
563
- // 로컬 데이터베이스에 대해서만 전체 마이그레이션에서 동작
564
- await migrator.runAction("apply", targets as (keyof SonamuDBConfig)[]);
555
+ await migrator.runAction("apply", getMigrateRunTargets());
565
556
  }
566
557
 
567
558
  async function migrate_generate() {
@@ -598,7 +589,7 @@ async function migrate_status() {
598
589
  }
599
590
 
600
591
  async function fixture_init() {
601
- const srcConfig = Sonamu.dbConfig.development_master;
592
+ const srcConfig = Sonamu.dbConfig[getSonamuEnvironment()];
602
593
  const targets = [
603
594
  {
604
595
  label: "(REMOTE) Fixture DB",
@@ -5,6 +5,7 @@ import { Sonamu } from "../api/sonamu";
5
5
  import { DB } from "../database/db";
6
6
  import { createKnexInstance } from "../database/knex";
7
7
  import { EntityManager } from "../entity/entity-manager";
8
+ import { getSonamuEnvironment } from "../env";
8
9
  import { DataExplorer } from "../testing/data-explorer";
9
10
  import { type DataExplorerStrategy } from "../testing/data-explorer";
10
11
  import { FixtureGenerator } from "../testing/fixture-generator";
@@ -129,12 +130,12 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
129
130
  const enableLLMCache = !options["no-cache"];
130
131
  const DEFAULT_PASSWORD = "Test1234!";
131
132
 
132
- // 로그인 가능 경로에서는 sourceDb로 development_master 사용
133
+ // 로그인 가능 경로에서는 현재 환경의 읽기 DB를 sourceDb로 사용
133
134
  const sourceDb = DB.getDB("r");
134
135
  const generator = new FixtureGenerator(
135
136
  sourceDb,
136
137
  sourceDb,
137
- "production_master",
138
+ getSonamuEnvironment(),
138
139
  EntityManager,
139
140
  { useLLM, enableLLMCache },
140
141
  );
@@ -395,8 +396,8 @@ export async function fixtureFetchCommand(options: FixtureCommandOptions) {
395
396
  const strategy: DataExplorerStrategy = options.strategy ?? "recent";
396
397
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 10;
397
398
 
398
- // fixture fetch: production 데이터를 fixture DB로 import합니다
399
- const sourceDb = DB.getDB("r"); // production_master (또는 development_master)
399
+ // fixture fetch: 현재 환경 데이터를 fixture DB로 import합니다
400
+ const sourceDb = DB.getDB("r");
400
401
  const fixtureDb = createKnexInstance(Sonamu.dbConfig.fixture);
401
402
  const generator = new FixtureGenerator(sourceDb, fixtureDb, "fixture", EntityManager);
402
403