sonamu 0.7.53 → 0.8.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 (271) hide show
  1. package/dist/api/config.d.ts +9 -1
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +1 -1
  4. package/dist/api/sonamu.d.ts +21 -1
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +159 -65
  7. package/dist/auth/plugins/entity-definitions/anonymous.d.ts +10 -0
  8. package/dist/auth/plugins/entity-definitions/anonymous.d.ts.map +1 -0
  9. package/dist/auth/plugins/entity-definitions/anonymous.js +23 -0
  10. package/dist/auth/plugins/entity-definitions/api-key.d.ts +9 -0
  11. package/dist/auth/plugins/entity-definitions/api-key.d.ts.map +1 -0
  12. package/dist/auth/plugins/entity-definitions/api-key.js +199 -0
  13. package/dist/auth/plugins/entity-definitions/index.d.ts +6 -0
  14. package/dist/auth/plugins/entity-definitions/index.d.ts.map +1 -1
  15. package/dist/auth/plugins/entity-definitions/index.js +20 -2
  16. package/dist/auth/plugins/entity-definitions/jwt.d.ts +9 -0
  17. package/dist/auth/plugins/entity-definitions/jwt.d.ts.map +1 -0
  18. package/dist/auth/plugins/entity-definitions/jwt.js +67 -0
  19. package/dist/auth/plugins/entity-definitions/organization.d.ts +9 -0
  20. package/dist/auth/plugins/entity-definitions/organization.d.ts.map +1 -0
  21. package/dist/auth/plugins/entity-definitions/organization.js +424 -0
  22. package/dist/auth/plugins/entity-definitions/passkey.d.ts +10 -0
  23. package/dist/auth/plugins/entity-definitions/passkey.d.ts.map +1 -0
  24. package/dist/auth/plugins/entity-definitions/passkey.js +129 -0
  25. package/dist/auth/plugins/entity-definitions/sso.d.ts +10 -0
  26. package/dist/auth/plugins/entity-definitions/sso.d.ts.map +1 -0
  27. package/dist/auth/plugins/entity-definitions/sso.js +110 -0
  28. package/dist/auth/plugins/entity-definitions/types.d.ts +1 -1
  29. package/dist/auth/plugins/entity-definitions/types.d.ts.map +1 -1
  30. package/dist/auth/plugins/entity-definitions/types.js +1 -1
  31. package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
  32. package/dist/auth/plugins/wrappers/admin.js +2 -4
  33. package/dist/auth/plugins/wrappers/anonymous.d.ts +18 -0
  34. package/dist/auth/plugins/wrappers/anonymous.d.ts.map +1 -0
  35. package/dist/auth/plugins/wrappers/anonymous.js +26 -0
  36. package/dist/auth/plugins/wrappers/api-key.d.ts +18 -0
  37. package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -0
  38. package/dist/auth/plugins/wrappers/api-key.js +38 -0
  39. package/dist/auth/plugins/wrappers/index.d.ts +6 -0
  40. package/dist/auth/plugins/wrappers/index.d.ts.map +1 -1
  41. package/dist/auth/plugins/wrappers/index.js +7 -1
  42. package/dist/auth/plugins/wrappers/jwt.d.ts +18 -0
  43. package/dist/auth/plugins/wrappers/jwt.d.ts.map +1 -0
  44. package/dist/auth/plugins/wrappers/jwt.js +30 -0
  45. package/dist/auth/plugins/wrappers/organization.d.ts +18 -0
  46. package/dist/auth/plugins/wrappers/organization.d.ts.map +1 -0
  47. package/dist/auth/plugins/wrappers/organization.js +67 -0
  48. package/dist/auth/plugins/wrappers/passkey.d.ts +18 -0
  49. package/dist/auth/plugins/wrappers/passkey.d.ts.map +1 -0
  50. package/dist/auth/plugins/wrappers/passkey.js +32 -0
  51. package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
  52. package/dist/auth/plugins/wrappers/phone-number.js +2 -4
  53. package/dist/auth/plugins/wrappers/sso.d.ts +853 -0
  54. package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -0
  55. package/dist/auth/plugins/wrappers/sso.js +36 -0
  56. package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
  57. package/dist/auth/plugins/wrappers/two-factor.js +2 -4
  58. package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
  59. package/dist/auth/plugins/wrappers/username.js +2 -4
  60. package/dist/bin/build-config.d.ts +2 -2
  61. package/dist/bin/build-config.js +6 -7
  62. package/dist/bin/cli.js +417 -32
  63. package/dist/bin/fixture.d.ts +27 -0
  64. package/dist/bin/fixture.d.ts.map +1 -0
  65. package/dist/bin/fixture.js +245 -0
  66. package/dist/cache/decorator.d.ts +4 -3
  67. package/dist/cache/decorator.d.ts.map +1 -1
  68. package/dist/cache/decorator.js +5 -4
  69. package/dist/cone/cone-generator.d.ts +33 -0
  70. package/dist/cone/cone-generator.d.ts.map +1 -0
  71. package/dist/cone/cone-generator.js +286 -0
  72. package/dist/database/_batch_update.d.ts.map +1 -1
  73. package/dist/database/_batch_update.js +16 -2
  74. package/dist/database/puri-subset.test-d.js +1 -1
  75. package/dist/database/puri-subset.types.d.ts +1 -1
  76. package/dist/database/puri-subset.types.d.ts.map +1 -1
  77. package/dist/database/puri-subset.types.js +1 -1
  78. package/dist/database/puri.d.ts +4 -0
  79. package/dist/database/puri.d.ts.map +1 -1
  80. package/dist/database/puri.js +20 -2
  81. package/dist/database/upsert-builder.d.ts.map +1 -1
  82. package/dist/database/upsert-builder.js +19 -3
  83. package/dist/dict/en.d.ts +15 -0
  84. package/dist/dict/en.d.ts.map +1 -1
  85. package/dist/dict/en.js +2 -1
  86. package/dist/dict/ko.d.ts +15 -0
  87. package/dist/dict/ko.d.ts.map +1 -1
  88. package/dist/dict/ko.js +2 -1
  89. package/dist/dict/rc-keys.d.ts +28 -0
  90. package/dist/dict/rc-keys.d.ts.map +1 -1
  91. package/dist/dict/rc-keys.js +31 -1
  92. package/dist/dict/sd.d.ts.map +1 -1
  93. package/dist/dict/sd.js +20 -4
  94. package/dist/entity/entity-manager.d.ts +298 -2
  95. package/dist/entity/entity-manager.d.ts.map +1 -1
  96. package/dist/entity/entity-manager.js +4 -1
  97. package/dist/entity/entity-template-cone.d.ts +14 -0
  98. package/dist/entity/entity-template-cone.d.ts.map +1 -0
  99. package/dist/entity/entity-template-cone.js +222 -0
  100. package/dist/entity/entity.d.ts +47 -2
  101. package/dist/entity/entity.d.ts.map +1 -1
  102. package/dist/entity/entity.js +161 -14
  103. package/dist/ssr/renderer.js +3 -3
  104. package/dist/syncer/api-parser.js +12 -1
  105. package/dist/syncer/checksum.d.ts +0 -14
  106. package/dist/syncer/checksum.d.ts.map +1 -1
  107. package/dist/syncer/checksum.js +1 -23
  108. package/dist/syncer/syncer-actions.d.ts.map +1 -1
  109. package/dist/syncer/syncer-actions.js +8 -2
  110. package/dist/syncer/syncer.d.ts +1 -1
  111. package/dist/syncer/syncer.d.ts.map +1 -1
  112. package/dist/syncer/syncer.js +17 -10
  113. package/dist/tasks/workflow-manager.d.ts +13 -1
  114. package/dist/tasks/workflow-manager.d.ts.map +1 -1
  115. package/dist/tasks/workflow-manager.js +18 -1
  116. package/dist/template/entity-converter.js +4 -4
  117. package/dist/template/helpers.d.ts +10 -0
  118. package/dist/template/helpers.d.ts.map +1 -1
  119. package/dist/template/helpers.js +48 -1
  120. package/dist/template/implementations/entry-server.template.d.ts +1 -1
  121. package/dist/template/implementations/entry-server.template.js +7 -2
  122. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  123. package/dist/template/implementations/generated.template.js +5 -1
  124. package/dist/template/implementations/generated_http.template.d.ts +1 -0
  125. package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
  126. package/dist/template/implementations/generated_http.template.js +6 -2
  127. package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
  128. package/dist/template/implementations/generated_sso.template.js +29 -8
  129. package/dist/template/implementations/queries.template.d.ts.map +1 -1
  130. package/dist/template/implementations/queries.template.js +9 -1
  131. package/dist/template/implementations/sd.template.d.ts +1 -1
  132. package/dist/template/implementations/sd.template.d.ts.map +1 -1
  133. package/dist/template/implementations/sd.template.js +28 -4
  134. package/dist/template/implementations/services.template.d.ts.map +1 -1
  135. package/dist/template/implementations/services.template.js +12 -12
  136. package/dist/template/implementations/view_form.template.d.ts +11 -7
  137. package/dist/template/implementations/view_form.template.d.ts.map +1 -1
  138. package/dist/template/implementations/view_form.template.js +97 -87
  139. package/dist/template/implementations/view_list.template.d.ts +3 -3
  140. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  141. package/dist/template/implementations/view_list.template.js +115 -109
  142. package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
  143. package/dist/template/implementations/view_search_input.template.js +18 -14
  144. package/dist/template/zod-converter.d.ts.map +1 -1
  145. package/dist/template/zod-converter.js +95 -7
  146. package/dist/testing/_relation-graph.js +1 -1
  147. package/dist/testing/data-explorer.d.ts +61 -0
  148. package/dist/testing/data-explorer.d.ts.map +1 -0
  149. package/dist/testing/data-explorer.js +274 -0
  150. package/dist/testing/faker-mappings.d.ts +20 -0
  151. package/dist/testing/faker-mappings.d.ts.map +1 -0
  152. package/dist/testing/faker-mappings.js +421 -0
  153. package/dist/testing/fixture-generator.d.ts +161 -0
  154. package/dist/testing/fixture-generator.d.ts.map +1 -0
  155. package/dist/testing/fixture-generator.js +954 -0
  156. package/dist/testing/fixture-manager.d.ts +6 -1
  157. package/dist/testing/fixture-manager.d.ts.map +1 -1
  158. package/dist/testing/fixture-manager.js +72 -4
  159. package/dist/testing/index.d.ts +3 -0
  160. package/dist/testing/index.d.ts.map +1 -1
  161. package/dist/testing/index.js +4 -1
  162. package/dist/types/types.d.ts +1520 -26
  163. package/dist/types/types.d.ts.map +1 -1
  164. package/dist/types/types.js +136 -22
  165. package/dist/ui/ai-client.d.ts.map +1 -1
  166. package/dist/ui/ai-client.js +9 -4
  167. package/dist/ui/api.d.ts.map +1 -1
  168. package/dist/ui/api.js +303 -24
  169. package/dist/ui-web/assets/index-CsUr-_pV.js +254 -0
  170. package/dist/ui-web/assets/index-T42zzs1K.css +1 -0
  171. package/dist/ui-web/index.html +2 -2
  172. package/dist/utils/fs-utils.d.ts +2 -1
  173. package/dist/utils/fs-utils.d.ts.map +1 -1
  174. package/dist/utils/fs-utils.js +14 -3
  175. package/package.json +19 -11
  176. package/src/api/config.ts +12 -1
  177. package/src/api/sonamu.ts +179 -65
  178. package/src/auth/plugins/entity-definitions/anonymous.ts +17 -0
  179. package/src/auth/plugins/entity-definitions/api-key.ts +93 -0
  180. package/src/auth/plugins/entity-definitions/index.ts +18 -0
  181. package/src/auth/plugins/entity-definitions/jwt.ts +35 -0
  182. package/src/auth/plugins/entity-definitions/organization.ts +215 -0
  183. package/src/auth/plugins/entity-definitions/passkey.ts +64 -0
  184. package/src/auth/plugins/entity-definitions/sso.ts +62 -0
  185. package/src/auth/plugins/entity-definitions/types.ts +11 -1
  186. package/src/auth/plugins/wrappers/admin.ts +1 -3
  187. package/src/auth/plugins/wrappers/anonymous.ts +30 -0
  188. package/src/auth/plugins/wrappers/api-key.ts +42 -0
  189. package/src/auth/plugins/wrappers/index.ts +6 -0
  190. package/src/auth/plugins/wrappers/jwt.ts +34 -0
  191. package/src/auth/plugins/wrappers/organization.ts +73 -0
  192. package/src/auth/plugins/wrappers/passkey.ts +36 -0
  193. package/src/auth/plugins/wrappers/phone-number.ts +1 -3
  194. package/src/auth/plugins/wrappers/sso.ts +40 -0
  195. package/src/auth/plugins/wrappers/two-factor.ts +1 -3
  196. package/src/auth/plugins/wrappers/username.ts +1 -3
  197. package/src/bin/build-config.ts +6 -6
  198. package/src/bin/cli.ts +452 -31
  199. package/src/bin/fixture.ts +302 -0
  200. package/src/cache/decorator.ts +4 -3
  201. package/src/cone/cone-generator.ts +363 -0
  202. package/src/database/_batch_update.ts +11 -0
  203. package/src/database/puri-subset.test-d.ts +13 -13
  204. package/src/database/puri-subset.types.ts +1 -1
  205. package/src/database/puri.ts +43 -1
  206. package/src/database/upsert-builder.ts +16 -2
  207. package/src/dict/en.ts +1 -0
  208. package/src/dict/ko.ts +1 -0
  209. package/src/dict/rc-keys.ts +32 -0
  210. package/src/dict/sd.ts +23 -3
  211. package/src/entity/entity-manager.ts +4 -0
  212. package/src/entity/entity-template-cone.ts +298 -0
  213. package/src/entity/entity.ts +189 -13
  214. package/src/shared/app.shared.ts.txt +5 -0
  215. package/src/shared/web.shared.ts.txt +9 -5
  216. package/src/skills/project/README.md +21 -0
  217. package/src/skills/project/architecture.md +373 -0
  218. package/src/skills/project/business-logic.md +270 -0
  219. package/src/skills/project/requirements.md +160 -0
  220. package/src/skills/sonamu/SKILL.md +168 -3
  221. package/src/skills/sonamu/api.md +102 -0
  222. package/src/skills/sonamu/database.md +220 -1
  223. package/src/skills/sonamu/entity-relations.md +89 -1
  224. package/src/skills/sonamu/fixture-cli.md +501 -0
  225. package/src/skills/sonamu/frontend.md +214 -0
  226. package/src/skills/sonamu/i18n.md +95 -0
  227. package/src/skills/sonamu/model.md +153 -0
  228. package/src/skills/sonamu/project-init.md +178 -8
  229. package/src/skills/sonamu/scaffolding.md +112 -0
  230. package/src/skills/sonamu/subset.md +9 -3
  231. package/src/skills/sonamu/testing.md +287 -2
  232. package/src/skills/sonamu/workflow.md +70 -5
  233. package/src/ssr/renderer.ts +2 -2
  234. package/src/syncer/api-parser.ts +12 -0
  235. package/src/syncer/checksum.ts +0 -38
  236. package/src/syncer/syncer-actions.ts +7 -1
  237. package/src/syncer/syncer.ts +16 -5
  238. package/src/tasks/workflow-manager.ts +29 -8
  239. package/src/template/entity-converter.ts +3 -3
  240. package/src/template/helpers.ts +49 -0
  241. package/src/template/implementations/entry-server.template.ts +1 -1
  242. package/src/template/implementations/generated.template.ts +4 -0
  243. package/src/template/implementations/generated_http.template.ts +1 -0
  244. package/src/template/implementations/generated_sso.template.ts +40 -11
  245. package/src/template/implementations/queries.template.ts +8 -0
  246. package/src/template/implementations/sd.template.ts +22 -3
  247. package/src/template/implementations/services.template.ts +11 -10
  248. package/src/template/implementations/view_form.template.ts +111 -101
  249. package/src/template/implementations/view_list.template.ts +120 -119
  250. package/src/template/implementations/view_search_input.template.ts +17 -13
  251. package/src/template/zod-converter.ts +103 -6
  252. package/src/testing/_relation-graph.ts +1 -1
  253. package/src/testing/data-explorer.ts +427 -0
  254. package/src/testing/faker-mappings.ts +434 -0
  255. package/src/testing/fixture-generator.ts +1166 -0
  256. package/src/testing/fixture-manager.ts +91 -6
  257. package/src/testing/index.ts +3 -0
  258. package/src/types/types.ts +222 -26
  259. package/src/ui/ai-client.ts +9 -1
  260. package/src/ui/api.ts +429 -23
  261. package/src/utils/fs-utils.ts +14 -1
  262. package/dist/template/implementations/view_enums_select.template.d.ts +0 -17
  263. package/dist/template/implementations/view_enums_select.template.d.ts.map +0 -1
  264. package/dist/template/implementations/view_enums_select.template.js +0 -62
  265. package/dist/template/implementations/view_id_async_select.template.d.ts +0 -17
  266. package/dist/template/implementations/view_id_async_select.template.d.ts.map +0 -1
  267. package/dist/template/implementations/view_id_async_select.template.js +0 -125
  268. package/dist/ui-web/assets/index-Bd_2AkLb.css +0 -1
  269. package/dist/ui-web/assets/index-BpSbhQWo.js +0 -225
  270. package/src/template/implementations/view_enums_select.template.ts +0 -65
  271. package/src/template/implementations/view_id_async_select.template.ts +0 -139
@@ -143,6 +143,7 @@ export const PostSaveParams = PostBaseSchema
143
143
  | "존재하지 않는 모듈 패스 요청 {Type}" | types.ts 미생성 또는 미컴파일 | 대기/수동생성 → build → dev 재시작 |
144
144
  | exhaustive() 타입 에러 | OrderBy 첫 번째 값만 자동 처리 | 위 "4. OrderBy 케이스 추가" 참조 |
145
145
  | i18n 키 없음 (relation) | `author_id` vs `author` | 위 "3. Relation이 있는 경우" 참조 |
146
+ | IdAsyncSelect API 불일치 | 구버전 scaffolding 템플릿 사용 | 아래 "IdAsyncSelect API 마이그레이션" 참조 |
146
147
 
147
148
  ## 상세 설명
148
149
 
@@ -283,3 +284,114 @@ export default {
283
284
  ```
284
285
 
285
286
  **권장**: 첫 번째 방법 - ko.ts에 `_id` 키 추가. sync 시 유지되며 여러 form에서 재사용 가능.
287
+
288
+ ### IdAsyncSelect API 마이그레이션
289
+
290
+ #### 발생 배경
291
+
292
+ Sonamu의 `@sonamu-kit/react-components` 패키지가 업데이트되면서 IdAsyncSelect API가 변경되었으나, scaffolding 생성 코드(`scaffolding/react-components.ts`)는 구 API 기준으로 코드를 생성합니다.
293
+
294
+ 따라서 `pnpm sonamu scaffold` 실행 시 구 API 기반 래퍼 컴포넌트가 생성되며, 최신 패키지를 사용하는 프로젝트에서는 빌드 오류가 발생합니다.
295
+
296
+ #### 구체적인 API 변경 사항
297
+
298
+ **구 API (scaffolding이 생성하는 코드):**
299
+ ```typescript
300
+ export function UserIdAsyncSelect<T extends UserSubsetKey>({
301
+ subset,
302
+ value,
303
+ onValueChange,
304
+ listParams, // ← 구 API
305
+ textField = "name", // ← 구 API
306
+ pageField, // ← 구 API
307
+ ...
308
+ }: UserIdAsyncSelectProps<T>) {
309
+ // 수동 상태 관리
310
+ const [searchText, setSearchText] = useState("");
311
+
312
+ const handleSearch = useCallback((text: string) => {
313
+ setSearchText(text);
314
+ }, []);
315
+
316
+ return (
317
+ <AsyncSelect // ← 구 컴포넌트
318
+ config={UserAsyncIdConfig}
319
+ subset={subset}
320
+ listParams={{ ...listParams, [textField]: searchText }}
321
+ textField={textField}
322
+ pageField={pageField}
323
+ onSearch={handleSearch}
324
+ ...
325
+ />
326
+ );
327
+ }
328
+ ```
329
+
330
+ **신 API (실제 패키지 API):**
331
+ ```typescript
332
+ export function UserIdAsyncSelect<T extends UserSubsetKey>({
333
+ subset,
334
+ value,
335
+ onValueChange,
336
+ baseListParams, // ← 신 API
337
+ displayField = "name", // ← 신 API
338
+ // pageField 없음 // ← 제거됨
339
+ ...
340
+ }: UserIdAsyncSelectProps<T>) {
341
+ // 상태 관리 없음 (내부에서 처리)
342
+
343
+ return (
344
+ <IdAsyncSelect<number> // ← 신 컴포넌트 + 제네릭
345
+ config={UserAsyncIdConfig}
346
+ subset={subset}
347
+ baseListParams={baseListParams}
348
+ displayField={displayField}
349
+ // 내부에서 검색 처리
350
+ ...
351
+ />
352
+ );
353
+ }
354
+ ```
355
+
356
+ #### 주요 변경점
357
+
358
+ 1. **컴포넌트명**: `AsyncSelect` → `IdAsyncSelect<T>` (제네릭 추가)
359
+ 2. **Props 이름**:
360
+ - `listParams` → `baseListParams`
361
+ - `textField` → `displayField`
362
+ - `pageField` 삭제
363
+ 3. **검색 로직**: 외부 상태관리 → 내부 처리 (useState, useCallback, onSearch 불필요)
364
+ 4. **제네릭 타입**: PK 타입 명시 필요 (`<number>` 또는 `<string>`)
365
+
366
+ #### 수정이 필요한 파일들
367
+
368
+ ```
369
+ src/components/
370
+ ├── user/UserIdAsyncSelect.tsx
371
+ ├── account/AccountIdAsyncSelect.tsx
372
+ ├── announcement/AnnouncementIdAsyncSelect.tsx
373
+ └── ... (모든 *IdAsyncSelect.tsx 파일)
374
+ ```
375
+
376
+ #### 마이그레이션 체크리스트
377
+
378
+ - [ ] 컴포넌트 import 변경: `AsyncSelect` → `IdAsyncSelect`
379
+ - [ ] 제네릭 타입 파라미터 추가: `<number>` 또는 `<string>` (PK 타입에 따라)
380
+ - [ ] Props 타입 정의 업데이트:
381
+ - [ ] `listParams` → `baseListParams`
382
+ - [ ] `textField` → `displayField`
383
+ - [ ] `pageField` 제거
384
+ - [ ] 수동 상태 관리 제거:
385
+ - [ ] `useState`, `useCallback` 제거
386
+ - [ ] `onSearch` 핸들러 제거
387
+ - [ ] JSX 내 props 이름 변경:
388
+ - [ ] `listParams={...}` → `baseListParams={...}`
389
+ - [ ] `textField={...}` → `displayField={...}`
390
+ - [ ] `pageField` 제거
391
+ - [ ] `onSearch` 제거
392
+
393
+ #### 왜 이런 일이 발생하나?
394
+
395
+ Sonamu의 scaffolding 생성 코드가 최신 패키지 API를 반영하지 못한 상태에서, 사용자가 로컬의 Sonamu 소스를 수정하면서 패키지는 업데이트되었지만 scaffolding 템플릿은 그대로인 상황에서 발생합니다.
396
+
397
+ **해결책**: 생성된 컴포넌트를 위 체크리스트에 따라 수동으로 수정하거나, Sonamu 코어의 scaffolding 템플릿을 최신 API로 업데이트해야 합니다.
@@ -134,9 +134,15 @@ const user = await UserModel.findById("P", 1);
134
134
  // findMany
135
135
  const { rows } = await UserModel.findMany("P", { num: 20, page: 1 });
136
136
 
137
- // getPuri + Subset (Puri는 Thenable이므로 직접 await)
138
- const users = await UserModel.getPuri("r", ["P"])
139
- .where("employee__department.name", "Engineering");
137
+ // getSubsetQueries + executeSubsetQuery
138
+ const { qb } = UserModel.getSubsetQueries("P");
139
+ qb.where("users.role", "admin");
140
+
141
+ const result = await UserModel.executeSubsetQuery({
142
+ subset: "P",
143
+ qb,
144
+ params: { num: 20, page: 1 },
145
+ });
140
146
  ```
141
147
 
142
148
  ## 주의사항
@@ -11,6 +11,10 @@ Sonamu는 Vitest 기반 테스트 환경을 제공한다. 각 테스트는 트
11
11
 
12
12
  **WARNING: 엔티티 10개 이상 프로젝트는 반드시 배치 전략 사용** (아래 "대규모 프로젝트 전략" 참고)
13
13
 
14
+ **참고 문서**:
15
+ - **Fixture CLI 명령어**: `fixture-cli.md` - fixture gen/fetch/explore 사용법, 3-Tier DB 구조
16
+ - **Fixture 생성 팁**: 이 문서 하단 "Fixture 데이터 생성 팁" 섹션 또는 `fixture-cli.md` "실전 팁" 섹션
17
+
14
18
  ---
15
19
 
16
20
  ## Quick Start - 테스트 작성 빠른 시작
@@ -638,7 +642,7 @@ status: "completed" // entity.json의 정확한 값
638
642
 
639
643
  단순 우선순위가 아닌, **업무 흐름 단위**로 엔티티를 묶는다.
640
644
 
641
- **VOC 시스템 예시:**
645
+ **고객 상담 시스템 예시:**
642
646
 
643
647
  ```
644
648
  그룹 1: 기반 인프라
@@ -2051,7 +2055,7 @@ await expect(UserModel.findById("A", 99999))
2051
2055
  // packages/api/package.json
2052
2056
  {
2053
2057
  "dependencies": {
2054
- "sonamu": "0.7.50" // link 대신 버전 명시
2058
+ "sonamu": "0.8.0" // link 대신 버전 명시
2055
2059
  }
2056
2060
  }
2057
2061
  ```
@@ -2324,3 +2328,284 @@ describe("TaskModel", () => {
2324
2328
  });
2325
2329
  });
2326
2330
  ```
2331
+
2332
+ ---
2333
+
2334
+ ## 흔한 실수와 해결 방법
2335
+
2336
+ ### ubUpsert는 unique constraint 에러를 던지지 않습니다
2337
+
2338
+ ubUpsert는 INSERT 실패 시 UPDATE를 수행하므로 unique constraint 위반 시에도 에러를 던지지 않습니다.
2339
+
2340
+ ```typescript
2341
+ // BAD: 이 테스트는 항상 실패
2342
+ test("코드 중복 방지 (unique 제약)", async () => {
2343
+ await createTestDepartment({ code: "IT" });
2344
+
2345
+ // ubUpsert는 에러를 던지지 않고 UPDATE를 수행
2346
+ await expect(
2347
+ createTestDepartment({ code: "IT" })
2348
+ ).rejects.toThrow();
2349
+ });
2350
+
2351
+ // GOOD: skip 처리하고 이유를 명시
2352
+ test.skip("코드 중복 방지 - ubUpsert는 중복 시 업데이트 수행", async () => {
2353
+ const code = "IT";
2354
+ await createTestDepartment({ code });
2355
+
2356
+ // 같은 코드로 두 번째 생성 시도
2357
+ await expect(
2358
+ createTestDepartment({ code })
2359
+ ).rejects.toThrow();
2360
+ });
2361
+ ```
2362
+
2363
+ **이유:** Sonamu의 ubUpsert 패턴은 INSERT ... ON DUPLICATE KEY UPDATE를 사용하므로 중복 키가 있어도 에러 대신 업데이트를 수행합니다.
2364
+
2365
+ **대안:** unique constraint 검증이 필요한 경우 직접 Knex 쿼리로 테스트하거나, 비즈니스 로직에서 중복 체크를 구현합니다.
2366
+
2367
+ ### Transaction isolation과 테스트 격리
2368
+
2369
+ 각 테스트는 독립된 트랜잭션에서 실행되므로 데이터가 격리됩니다. 같은 테스트 내에서도 생성한 데이터가 쿼리에 즉시 보이지 않을 수 있습니다.
2370
+
2371
+ ```typescript
2372
+ // BAD: 정확한 개수를 기대하면 실패할 수 있음
2373
+ test("역할명 검색", async () => {
2374
+ await createTestRole({ name: "관리자A" });
2375
+ await createTestRole({ name: "관리자B" });
2376
+
2377
+ const { rows } = await RoleModel.findMany("A", {
2378
+ keyword: "관리자"
2379
+ });
2380
+
2381
+ // Transaction isolation으로 인해 2개가 보이지 않을 수 있음
2382
+ expect(rows.length).toBe(2);
2383
+ });
2384
+
2385
+ // GOOD: 고유 식별자와 유연한 assertion 사용
2386
+ test("역할명 검색", async () => {
2387
+ // 고유한 식별자로 충돌 방지
2388
+ const testName = `검색테스트_${Date.now()}`;
2389
+ await createTestRole({ name: `${testName}A` });
2390
+ await createTestRole({ name: `${testName}B` });
2391
+
2392
+ const { rows } = await RoleModel.findMany("A", {
2393
+ keyword: testName
2394
+ });
2395
+
2396
+ // 최소 1개 이상 확인
2397
+ expect(rows.length).toBeGreaterThanOrEqual(1);
2398
+ // 내용 검증
2399
+ expect(rows.some(r => r.name.includes(testName))).toBe(true);
2400
+ });
2401
+ ```
2402
+
2403
+ **패턴:**
2404
+ - 고유 식별자 사용: `Date.now()`, `uuid()` 등으로 충돌 방지
2405
+ - 유연한 assertion: `toBeGreaterThanOrEqual(1)` 대신 `toBe(2)` 사용
2406
+ - 내용 검증: 개수보다 실제 데이터가 맞는지 확인
2407
+
2408
+ ### 정렬 테스트의 조건부 검증
2409
+
2410
+ 정렬 테스트에서 모든 데이터가 조회되지 않을 수 있으므로 조건부 검증을 사용합니다:
2411
+
2412
+ ```typescript
2413
+ // BAD: 항상 두 항목이 조회된다고 가정
2414
+ test("정렬 - ID 최신순", async () => {
2415
+ const id1 = await createTestRole({ name: "역할1" });
2416
+ const id2 = await createTestRole({ name: "역할2" });
2417
+
2418
+ const { rows } = await RoleModel.findMany("A", {
2419
+ orderBy: "id-desc",
2420
+ });
2421
+
2422
+ const id2Index = rows.findIndex(r => r.id === id2);
2423
+ const id1Index = rows.findIndex(r => r.id === id1);
2424
+
2425
+ // 둘 중 하나라도 없으면 실패
2426
+ expect(id2Index).toBeLessThan(id1Index);
2427
+ });
2428
+
2429
+ // GOOD: 조건부 검증
2430
+ test("정렬 - ID 최신순", async () => {
2431
+ const id1 = await createTestRole({ name: "역할1" });
2432
+ const id2 = await createTestRole({ name: "역할2" });
2433
+
2434
+ const { rows } = await RoleModel.findMany("A", {
2435
+ orderBy: "id-desc",
2436
+ });
2437
+
2438
+ const testRoles = rows.filter(r => [id1, id2].includes(r.id));
2439
+ expect(testRoles.length).toBeGreaterThanOrEqual(1);
2440
+
2441
+ // 두 역할이 모두 조회된 경우에만 순서 검증
2442
+ if (testRoles.length === 2) {
2443
+ const id2Index = rows.findIndex(r => r.id === id2);
2444
+ const id1Index = rows.findIndex(r => r.id === id1);
2445
+ expect(id2Index).toBeLessThan(id1Index);
2446
+ }
2447
+ });
2448
+ ```
2449
+
2450
+ **핵심:** Transaction isolation으로 인한 불확실성을 받아들이고, 검증 가능한 경우에만 assertion을 수행합니다.
2451
+
2452
+ ---
2453
+
2454
+ ## Fixture 데이터 생성 팁
2455
+
2456
+ Sonamu의 `pnpm sonamu fixture gen` 명령어로 테스트용 fixture 데이터를 생성할 때 유용한 패턴들입니다.
2457
+
2458
+ ### Unique Constraint 고려
2459
+
2460
+ unique constraint가 있는 필드는 랜덤 데이터 생성 시 중복을 피해야 합니다.
2461
+
2462
+ **문제 상황:**
2463
+ ```typescript
2464
+ // BAD - 같은 값 반복 생성 → unique constraint 위반
2465
+ const dept = faker.helpers.arrayElement(["개발팀", "기획팀", "마케팅팀"]);
2466
+ // 같은 company_id에 "개발팀"이 여러 번 생성되면 오류 발생
2467
+ ```
2468
+
2469
+ **해결 방법: Suffix/Prefix 추가로 중복 방지**
2470
+ ```typescript
2471
+ // GOOD - 70% 확률로 suffix/prefix 추가하여 변형
2472
+ const depts = ["개발팀", "기획팀", "마케팅팀", "영업팀", "인사팀"];
2473
+ const prefixes = ["신규", "통합", "전략", "글로벌", "디지털", "핵심"];
2474
+ const suffixes = ["1팀", "2팀", "3팀", "A팀", "B팀", "본부", "센터", "그룹"];
2475
+
2476
+ const dept = faker.helpers.arrayElement(depts);
2477
+ const random = Math.random();
2478
+
2479
+ if (random > 0.7) {
2480
+ return `${faker.helpers.arrayElement(prefixes)} ${dept}`;
2481
+ }
2482
+ if (random > 0.4) {
2483
+ return `${dept} ${faker.helpers.arrayElement(suffixes)}`;
2484
+ }
2485
+ return dept;
2486
+
2487
+ // 결과 예시: "개발팀", "개발팀 1팀", "글로벌 개발팀", "개발팀 본부" 등
2488
+ ```
2489
+
2490
+ **핵심:**
2491
+ - 기본 값(30% 확률) + 변형 값(70% 확률)으로 중복 최소화
2492
+ - unique constraint가 있는 필드는 반드시 변형 로직 추가
2493
+ - 여러 조합으로 다양성 확보 (prefix × dept, dept × suffix)
2494
+
2495
+ ### 한국어 데이터 생성
2496
+
2497
+ 테스트 데이터의 가독성을 높이기 위해 한국어 데이터를 생성합니다.
2498
+
2499
+ **설치:**
2500
+ ```bash
2501
+ pnpm add -D @faker-js/faker
2502
+ ```
2503
+
2504
+ **사용 예시:**
2505
+ ```typescript
2506
+ import { faker } from "@faker-js/faker";
2507
+ import { fakerKO } from "@faker-js/faker";
2508
+
2509
+ // 한국 이름 (성+이름)
2510
+ const name = fakerKO.person.fullName();
2511
+ // 예: "김민준", "이서연", "박지호"
2512
+
2513
+ // 한국 성만
2514
+ const lastName = fakerKO.person.lastName();
2515
+ // 예: "김", "이", "박"
2516
+
2517
+ // 한국 이름만
2518
+ const firstName = fakerKO.person.firstName();
2519
+ // 예: "민준", "서연", "지호"
2520
+
2521
+ // 한국 부서명 (커스텀 리스트)
2522
+ const departments = [
2523
+ "개발팀", "기획팀", "마케팅팀", "영업팀", "인사팀",
2524
+ "재무팀", "법무팀", "품질관리팀", "IT팀", "디자인팀"
2525
+ ];
2526
+ const dept = faker.helpers.arrayElement(departments);
2527
+
2528
+ // 한국 회사명 (커스텀 리스트 + suffix)
2529
+ const companies = ["테크놀로지", "솔루션즈", "디지털", "이노베이션"];
2530
+ const suffixes = ["주식회사", "㈜", "코퍼레이션", "그룹"];
2531
+ const company = `${faker.helpers.arrayElement(companies)} ${faker.helpers.arrayElement(suffixes)}`;
2532
+ // 예: "테크놀로지 주식회사", "디지털 ㈜"
2533
+ ```
2534
+
2535
+ **FixtureGenerator에서 활용:**
2536
+ ```typescript
2537
+ // fixture-generator.ts 내부
2538
+ if (entity?.id === "Department" && prop.name === "name") {
2539
+ const departments = ["개발팀", "기획팀", "마케팅팀", "영업팀"];
2540
+ const prefixes = ["신규", "통합", "전략", "글로벌"];
2541
+ const suffixes = ["1팀", "2팀", "본부", "센터"];
2542
+
2543
+ const dept = faker.helpers.arrayElement(departments);
2544
+ const random = Math.random();
2545
+
2546
+ if (random > 0.7) {
2547
+ return `${faker.helpers.arrayElement(prefixes)} ${dept}`;
2548
+ }
2549
+ if (random > 0.4) {
2550
+ return `${dept} ${faker.helpers.arrayElement(suffixes)}`;
2551
+ }
2552
+ return dept;
2553
+ }
2554
+
2555
+ if (prop.name === "name" || prop.name === "username") {
2556
+ return fakerKO.person.fullName();
2557
+ }
2558
+ ```
2559
+
2560
+ ### Fixture Gen vs Fetch 선택 기준
2561
+
2562
+ | 상황 | 명령어 | 이유 |
2563
+ |------|--------|------|
2564
+ | 운영 DB에 데이터가 없음 | `fixture gen` | faker로 더미 데이터 생성 |
2565
+ | 실제 데이터로 테스트 필요 | `fixture fetch` | 운영 DB에서 가져오기 |
2566
+ | 특정 패턴 데이터 필요 | `fixture gen` + custom logic | FixtureGenerator 수정 |
2567
+ | 관련 데이터 함께 필요 | `fixture fetch` | FK 따라 자동으로 가져옴 |
2568
+
2569
+ **예시:**
2570
+ ```bash
2571
+ # 더미 데이터 생성 (한국어)
2572
+ pnpm sonamu fixture gen --include Department --count 10
2573
+
2574
+ # 실제 데이터 가져오기 (최근 10개 + 관련 데이터)
2575
+ pnpm sonamu fixture fetch --include User --limit 10 --strategy recent
2576
+ ```
2577
+
2578
+ ### DB 시퀀스 리셋
2579
+
2580
+ Fixture 데이터 생성 후 ID 시퀀스가 실제 데이터와 맞지 않을 수 있습니다.
2581
+
2582
+ **확인:**
2583
+ ```bash
2584
+ PGPASSWORD=1234 psql -h 0.0.0.0 -U postgres -d project_fixture -c "
2585
+ SELECT
2586
+ schemaname,
2587
+ sequencename,
2588
+ last_value
2589
+ FROM pg_sequences
2590
+ WHERE schemaname = 'public'
2591
+ ORDER BY sequencename;
2592
+ "
2593
+ ```
2594
+
2595
+ **리셋:**
2596
+ ```sql
2597
+ -- 각 테이블마다 실행
2598
+ SELECT setval('departments_id_seq', (SELECT MAX(id) FROM departments), true);
2599
+ SELECT setval('companies_id_seq', (SELECT MAX(id) FROM companies), true);
2600
+ ```
2601
+
2602
+ **자동화 스크립트:**
2603
+ ```bash
2604
+ # 모든 시퀀스 리셋
2605
+ PGPASSWORD=1234 psql -h 0.0.0.0 -U postgres -d project_fixture -c "
2606
+ SELECT 'SELECT setval(''' || sequencename || ''', (SELECT COALESCE(MAX(id), 1) FROM ' ||
2607
+ replace(sequencename, '_id_seq', '') || '), true);'
2608
+ FROM pg_sequences
2609
+ WHERE schemaname = 'public' AND sequencename LIKE '%_id_seq';
2610
+ " | grep SELECT | PGPASSWORD=1234 psql -h 0.0.0.0 -U postgres -d project_fixture
2611
+ ```
@@ -16,21 +16,86 @@ description: Sonamu 전체 개발 워크플로우. 엔티티 설계부터 Fronte
16
16
  **참조 스킬:** entity-basic.md, entity-relations.md
17
17
 
18
18
  **절차:**
19
- 1. 요구사항 분석 (누락된 Entity 확인)
19
+
20
+ 1. **비즈니스 플로우 작성**
21
+ - 업무 프로세스를 단계별로 작성
22
+ - 각 단계의 입력/출력 데이터 명시
23
+ - 데이터 흐름 다이어그램 작성 (선택)
24
+ - 예: "공고 발행 → 과제 신청 → 평가 생성 → 평가위원 배정 → 평가표 작성 → 확정"
25
+
26
+ 2. **Entity 식별 및 역할 정의**
27
+ - 비즈니스 플로우 기반으로 필요한 모든 Entity 나열
28
+ - 각 Entity의 역할 명확히 정의
20
29
  - "사용자(User) Entity가 필요한가요?"
21
30
  - "추가로 필요한 Entity가 있나요?"
22
- 2. Entity 관계 확인 ( 번에 하나씩 질문)
31
+ - **중요**: 매핑 테이블(N:M 관계)도 Entity로 식별
32
+
33
+ 3. **Entity 간 관계 정의**
34
+ - ERD 작성 (시각화 권장)
35
+ - 관계 유형 결정 (BelongsToOne, HasMany, ManyToMany)
36
+ - FK 관계 명시
23
37
  - "챕터는 강좌의 자식으로 함께 관리할까요?"
24
38
  - "강좌와 수강생은 어떤 관계인가요?"
25
- 3. parentId 사용 여부 결정
26
- 4. 사용자 최종 확인
39
+ - **중요**: N:M 관계는 중간 매핑 Entity 필요
40
+
41
+ 4. **필드 목록 상세화**
42
+ - 각 Entity별로 필요한 필드 전부 나열
43
+ - 필드명, 타입, nullable 여부 결정
44
+ - 시간 추적 필드 확인 (created_at, updated_at, started_at 등)
45
+ - 상태 관리 필드 확인 (status, confirmed_date 등)
46
+ - **체크리스트**: "이 Entity에 누락된 필드는 없는가?"
47
+
48
+ 5. **parentId 사용 여부 결정**
49
+ - 계층 구조가 필요한 Entity 확인
50
+ - parentId 또는 별도 관계 테이블 선택
51
+
52
+ 6. **사용자 최종 확인**
53
+ - 전체 설계 리뷰
54
+ - 누락 사항 확인
55
+ - 승인
27
56
 
28
57
  **완료 기준:**
58
+ - [ ] 비즈니스 플로우 문서 작성 완료
29
59
  - [ ] 모든 필수 Entity 식별 완료
30
- - [ ] Entity 관계 정의 완료 (BelongsToOne, HasMany, ManyToMany)
60
+ - [ ] Entity 역할 정의 완료
61
+ - [ ] Entity 간 관계 정의 완료 (ERD 포함)
62
+ - [ ] **각 Entity별 필드 목록 작성 완료**
31
63
  - [ ] parentId 사용 여부 결정
32
64
  - [ ] 사용자 승인
33
65
 
66
+ **CRITICAL - API 개발 전 필수 단계:**
67
+
68
+ Entity 설계 후 API 개발로 넘어가기 전에 **반드시** 다음을 수행하세요:
69
+
70
+ **"추측하지 말고, 확인하라"**
71
+
72
+ - DON'T: 필드명 추측 금지 → Entity JSON 확인 필수
73
+ - DON'T: 관계 타입 추측 금지 → BelongsToOne은 자동으로 _id FK 생성
74
+ - DON'T: Subset 접근 추측 금지 → nested 접근 방식 확인 (예: committee.id, evaluator.id)
75
+
76
+ **API 개발 시작 전 체크리스트:**
77
+ ```
78
+ □ 1. 모든 Entity JSON 파일 읽기 완료
79
+ - 필드명 정확히 파악
80
+ - 타입 확인
81
+ - nullable 여부 확인
82
+
83
+ □ 2. 관계 확인
84
+ - BelongsToOne → _id FK 자동 생성 확인
85
+ - HasMany 방향 확인
86
+ - ManyToMany 매핑 테이블 확인
87
+
88
+ □ 3. Subset 구조 확인
89
+ - Subset A에서 관계 필드 접근 방식 확인
90
+ - nested object로 접근 (예: sheet.committee.id)
91
+ - FK 직접 접근 불가 (예: sheet.committee_id ❌)
92
+
93
+ □ 4. 기존 필드 활용 확인
94
+ - 시작/종료 시점: start_date, end_date 있는지 확인
95
+ - 상태 관리: status enum으로 충분한지 확인
96
+ - 불필요한 필드 추가 방지
97
+ ```
98
+
34
99
  **다음 단계:** PHASE 2 엔티티 생성
35
100
 
36
101
  ---
@@ -64,8 +64,8 @@ export async function renderSSR(
64
64
  } else {
65
65
  // Prod: 빌드된 파일
66
66
  const fs = await import("node:fs");
67
- const webDistPath = path.join(Sonamu.apiRootPath, "public", "web");
68
- const ssrPath = path.join(Sonamu.apiRootPath, "dist", "ssr");
67
+ const webDistPath = path.join(Sonamu.apiRootPath, "web-dist", "client");
68
+ const ssrPath = path.join(Sonamu.apiRootPath, "web-dist", "server");
69
69
 
70
70
  // 빌드된 index.html에서 스크립트 추출
71
71
  const builtHtml = fs.readFileSync(path.join(webDistPath, "index.html"), "utf-8");
@@ -234,6 +234,18 @@ function resolveTypeNode(typeNode: ts.TypeNode): ApiParamType {
234
234
  // 괄호로 묶인 타입 (예: (A & B)[] 에서 (A & B))
235
235
  // 내부 타입을 재귀적으로 resolve
236
236
  return resolveTypeNode((typeNode as ts.ParenthesizedTypeNode).type);
237
+
238
+ case ts.SyntaxKind.FunctionType:
239
+ return {
240
+ t: "function",
241
+ parameters: (typeNode as ts.FunctionTypeNode).parameters.map((param) => ({
242
+ name: param.name.getText(),
243
+ type: param.type ? resolveTypeNode(param.type) : "unknown",
244
+ optional: param.questionToken !== undefined,
245
+ defaultDef: undefined,
246
+ })),
247
+ returnType: resolveTypeNode((typeNode as ts.FunctionTypeNode).type),
248
+ };
237
249
  case undefined:
238
250
  throw new Error(`typeNode undefined`);
239
251
  }
@@ -48,38 +48,6 @@ export async function renewChecksums(): Promise<void> {
48
48
  await saveChecksums(calculatedChecksums);
49
49
  }
50
50
 
51
- export type FileOrData =
52
- | {
53
- path: PathLike;
54
- }
55
- | {
56
- data: string;
57
- };
58
-
59
- /**
60
- * 두 파일의 내용이 같은지 체크섬으로 비교합니다.
61
- * 만약 파일이 둘 중 하나라도 없다면 비교 불가로 false 반환합니다.
62
- * @param one 파일 경로 혹은 데이터
63
- * @param two 파일 경로 혹은 데이터
64
- * @returns boolean
65
- */
66
- export async function areFilesSame(...files: FileOrData[]): Promise<boolean> {
67
- const checksums: string[] = [];
68
-
69
- for (const file of files) {
70
- if ("path" in file && !(await exists(file.path))) {
71
- return false;
72
- }
73
-
74
- checksums.push(
75
- "path" in file ? await getChecksumOfFile(file.path) : getChecksumOfData(file.data),
76
- );
77
- }
78
-
79
- // 모든 체크섬이 첫 번째 체크섬과 같은지 확인
80
- return checksums.every((checksum) => checksum === checksums[0]);
81
- }
82
-
83
51
  async function getCurrentChecksums(): Promise<PathAndChecksum[]> {
84
52
  const filePaths = (
85
53
  await Promise.all(
@@ -147,12 +115,6 @@ async function saveChecksums(checksums: PathAndChecksum[]): Promise<void> {
147
115
  );
148
116
  }
149
117
 
150
- function getChecksumOfData(data: string): string {
151
- const hash = crypto.createHash("sha1");
152
- hash.update(data);
153
- return hash.digest("hex");
154
- }
155
-
156
118
  async function getChecksumOfFile(filePath: PathLike): Promise<string> {
157
119
  return new Promise<string>((resolve, reject) => {
158
120
  const hash = crypto.createHash("sha1");
@@ -113,7 +113,13 @@ export async function actionSyncFilesToTargets(tsPaths: AbsolutePath[]): Promise
113
113
  console.log(
114
114
  chalk.bold("Copied: ") + chalk.blue(dst.replace(`${Sonamu.appRootPath}/`, "")),
115
115
  );
116
- await copyFileWithReplaceCoreToShared(realSrc, dst);
116
+ const syncHeader = [
117
+ "/**",
118
+ " * @generated",
119
+ " * API에서 동기화된 파일입니다. 직접 수정하지 마세요.",
120
+ " */",
121
+ ].join("\n");
122
+ await copyFileWithReplaceCoreToShared(realSrc, dst, syncHeader);
117
123
  return dst;
118
124
  }),
119
125
  ),