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
@@ -9,7 +9,7 @@ export type BuildArtifact<BuildCommandArgs = {}> = {
9
9
 
10
10
  /**
11
11
  * API 프로젝트 빌드 산출물에 대한 규칙들.
12
- * cli.ts의 build 함수가 이것을 보고 그대로 실행합니다.
12
+ * cli.ts의 build_api 함수가 이것을 보고 그대로 실행합니다.
13
13
  */
14
14
  export const API_ARTIFACTS: BuildArtifact<{ configFilePath: string }>[] = [
15
15
  {
@@ -24,24 +24,24 @@ export const API_ARTIFACTS: BuildArtifact<{ configFilePath: string }>[] = [
24
24
 
25
25
  /**
26
26
  * 웹 프로젝트 빌드 산출물에 대한 규칙들.
27
- * cli.ts의 build 함수가 이것을 보고 그대로 실행합니다.
27
+ * cli.ts의 build_web 함수가 이것을 보고 그대로 실행합니다.
28
28
  */
29
29
  export const WEB_ARTIFACTS: BuildArtifact[] = [
30
30
  {
31
31
  name: "Web Client",
32
32
  description: "Web 프로젝트 클라이언트 빌드 산출물",
33
33
  projectPath: "web",
34
- preBuildCommand: () => "rm -rf dist/client && rm -rf ../api/public/web",
34
+ preBuildCommand: () => "rm -rf dist/client",
35
35
  buildCommand: () => "tsc --noEmit && vite build --outDir dist/client",
36
- postBuildCommand: () => "mkdir -p ../api/public/web && cp -r dist/client/* ../api/public/web",
37
36
  },
38
37
  {
39
38
  name: "Web Server",
40
39
  description: "Web 프로젝트 서버 빌드 산출물",
41
40
  projectPath: "web",
42
- preBuildCommand: () => "rm -rf dist/server", // api/dist/ssr은 안 지웁니다! 거기는 api의 빌드 결과물이 들어있음!
41
+ preBuildCommand: () => "rm -rf dist/server",
43
42
  buildCommand: () =>
44
43
  "tsc --noEmit && vite build --ssr src/entry-server.generated.tsx --outDir dist/server",
45
- postBuildCommand: () => "mkdir -p ../api/dist/ssr && cp -r dist/server/* ../api/dist/ssr",
44
+ postBuildCommand: () =>
45
+ "rm -rf ../api/web-dist && mkdir -p ../api/web-dist && cp -r dist/* ../api/web-dist",
46
46
  },
47
47
  ];
package/src/bin/cli.ts CHANGED
@@ -33,9 +33,45 @@ import {
33
33
  import { exists } from "../utils/fs-utils";
34
34
  import { findApiRootPath, findAppRootPath } from "../utils/utils";
35
35
  import { API_ARTIFACTS, type BuildArtifact, WEB_ARTIFACTS } from "./build-config";
36
+ import { fixtureExploreCommand, fixtureFetchCommand, fixtureGenCommand } from "./fixture";
36
37
 
37
38
  let migrator: Migrator;
38
39
 
40
+ /**
41
+ * CLI 옵션을 파싱하는 헬퍼 함수
42
+ */
43
+ function parseCliOptions(argv: string[] = process.argv): {
44
+ flags: Set<string>; // --regenerate, --ai, --no-cones 등
45
+ options: Record<string, string>; // --locale ko 등
46
+ } {
47
+ const flags = new Set<string>();
48
+ const options: Record<string, string> = {};
49
+
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i];
52
+ if (!arg.startsWith("--")) continue;
53
+
54
+ // --option=value 형식
55
+ if (arg.includes("=")) {
56
+ const [key, value] = arg.slice(2).split("=");
57
+ options[key] = value;
58
+ continue;
59
+ }
60
+
61
+ // --option value 형식인지 확인
62
+ const nextArg = argv[i + 1];
63
+ if (nextArg && !nextArg.startsWith("--") && !nextArg.startsWith("-")) {
64
+ options[arg.slice(2)] = nextArg;
65
+ i++; // 다음 arg는 값이므로 스킵
66
+ } else {
67
+ // --flag 형식
68
+ flags.add(arg.slice(2));
69
+ }
70
+ }
71
+
72
+ return { flags, options };
73
+ }
74
+
39
75
  async function bootstrap() {
40
76
  const notToInit = ["dev", "build", "start", "skills"].includes(process.argv[2] ?? "");
41
77
  if (!notToInit) {
@@ -45,23 +81,43 @@ async function bootstrap() {
45
81
  try {
46
82
  // tsicli는 정확한 명령어 매칭만 지원하므로, --로 시작하는 옵션과 그 값을 필터링합니다.
47
83
  // 옵션 파싱은 각 runner 함수에서 원본 process.argv를 사용하여 수행합니다.
84
+ // "--"(bare double dash)는 passthrough 구분자이므로, 그 뒤의 모든 인자도 제외합니다.
48
85
  const filteredArgv: string[] = [];
49
86
  let skipNext = false;
50
- for (const arg of process.argv) {
87
+ let afterDoubleDash = false;
88
+ for (let i = 0; i < process.argv.length; i++) {
89
+ const arg = process.argv[i];
90
+ if (arg === "--") {
91
+ afterDoubleDash = true;
92
+ continue;
93
+ }
94
+ if (afterDoubleDash) continue;
51
95
  if (skipNext) {
52
96
  skipNext = false;
53
97
  continue;
54
98
  }
55
99
  if (arg.startsWith("--")) {
56
100
  // --option=value 형식은 이 arg만 스킵
57
- // --option value 형식은 다음 arg 스킵
58
- if (!arg.includes("=")) {
101
+ if (arg.includes("=")) {
102
+ continue;
103
+ }
104
+ // --option value 형식인지 확인: 다음 arg가 --로 시작하지 않으면 값이 있는 것
105
+ const nextArg = process.argv[i + 1];
106
+ if (nextArg && !nextArg.startsWith("--") && !nextArg.startsWith("-")) {
59
107
  skipNext = true;
60
108
  }
61
109
  continue;
62
110
  }
63
111
  filteredArgv.push(arg);
64
112
  }
113
+
114
+ // build/dev 명령어가 서브커맨드 없이 호출될 때 "all"을 기본값으로 추가합니다.
115
+ // 예: `sonamu build` → `sonamu build all`, `sonamu dev` → `sonamu dev all`
116
+ const cmd = filteredArgv[2];
117
+ if ((cmd === "build" || cmd === "dev") && filteredArgv.length === 3) {
118
+ filteredArgv.push("all");
119
+ }
120
+
65
121
  await tsicli(filteredArgv, {
66
122
  types: {
67
123
  "#entityId": {
@@ -91,6 +147,9 @@ async function bootstrap() {
91
147
  ["fixture", "init"],
92
148
  ["fixture", "import", "#entityId", "#recordIds"],
93
149
  ["fixture", "sync"],
150
+ ["fixture", "gen"],
151
+ ["fixture", "fetch"],
152
+ ["fixture", "explore"],
94
153
  ["migrate", "run"],
95
154
  ["migrate", "apply", "#targets"],
96
155
  ["migrate", "status"],
@@ -100,9 +159,14 @@ async function bootstrap() {
100
159
  ["scaffold", "model_test", "#entityId"],
101
160
  ["scaffold", "view_list", "#entityId"],
102
161
  ["scaffold", "view_form", "#entityId"],
162
+ ["cone", "gen", "#entityId"],
103
163
  ["sync"],
104
- ["dev"],
105
- ["build"],
164
+ ["build", "all"],
165
+ ["build", "api"],
166
+ ["build", "web"],
167
+ ["dev", "all"],
168
+ ["dev", "api"],
169
+ ["dev", "web"],
106
170
  ["start"],
107
171
  ["skills", "sync"],
108
172
  ["skills", "create", "#name"],
@@ -115,15 +179,23 @@ async function bootstrap() {
115
179
  fixture_init,
116
180
  fixture_import,
117
181
  fixture_sync,
182
+ fixture_gen,
183
+ fixture_fetch,
184
+ fixture_explore,
118
185
  stub_practice,
119
186
  stub_entity,
120
187
  scaffold_model,
121
188
  scaffold_model_test,
122
189
  // scaffold_view_list,
123
190
  // scaffold_view_form,
191
+ cone_gen,
124
192
  sync,
125
- dev,
126
- build,
193
+ build_all,
194
+ build_api,
195
+ build_web,
196
+ dev_all,
197
+ dev_api,
198
+ dev_web,
127
199
  start,
128
200
  skills_sync,
129
201
  skills_create,
@@ -148,8 +220,8 @@ async function sync() {
148
220
  }
149
221
 
150
222
  /**
151
- * pnpm dev 하면 실행되는 함수입니다.
152
- * 프로젝트에 대해 HMR 지원하는 개발 서버를 띄워줍니다.
223
+ * API 개발 서버를 실행하는 공통 로직입니다.
224
+ * dev_all과 dev_api에서 공유합니다.
153
225
  *
154
226
  * TypeScript를 바로 실행할 수 있도록 @sonamu-kit/ts-loader를,
155
227
  * HMR을 지원하기 위해 @sonamu-kit/hmr-hook을 import하며,
@@ -158,15 +230,11 @@ async function sync() {
158
230
  * 이때 @sonamu-kit/ts-loader와 @sonamu-kit/hmr-hook는 sonamu가 자체적으로 가지고 있는 dependency입니다.
159
231
  * 또한 실행에 사용하는 @sonamu-kit/hmr-runner도 마찬가지로 sonamu가 자체적으로 가지고 있는 dependency입니다.
160
232
  * 따라서 사용자 프로젝트에서는 이 세 패키지를 직접 설치할 필요가 없습니다.
161
- *
162
- * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
163
233
  */
164
- async function dev() {
234
+ function spawnApiDevServer(options?: { extraEnv?: Record<string, string> }) {
165
235
  const apiRoot = findApiRootPath();
166
236
  const entryPoint = "src/index.ts";
167
237
 
168
- console.log(chalk.yellow.bold("🚀 Starting Sonamu dev server...\n"));
169
-
170
238
  // 이 sonamu 패키지가 dependencies로 가지고 있는 @sonamu-kit/hmr-runner의 bin/run.js를 사용합니다.
171
239
  // 이 경로(/bin/run.js)는 @sonamu-kit/hmr-runner의 package.json의 bin 필드에 명시되어 있는 그것과 같습니다.
172
240
  const hotRunnerBinPath = createRequire(import.meta.url).resolve(
@@ -196,6 +264,7 @@ async function dev() {
196
264
  NODE_ENV: "development",
197
265
  HOT: "yes", // 얘가 있어야 HMR이 활성화됩니다.
198
266
  API_ROOT_PATH: apiRoot, // 이 경로가 hmr-hook의 루트 디렉토리가 됩니다.
267
+ ...options?.extraEnv,
199
268
  },
200
269
  },
201
270
  );
@@ -212,34 +281,88 @@ async function dev() {
212
281
 
213
282
  serverProcess.on("exit", (code) => {
214
283
  if (code !== 0) {
215
- console.error(chalk.red(`❌ Server exited with code ${code}`));
284
+ console.error(chalk.red(`Server exited with code ${code}`));
216
285
  process.exit(code || 1);
217
286
  }
218
287
  });
219
288
  }
220
289
 
221
290
  /**
222
- * pnpm build 하면 실행되는 함수입니다.
223
- * 프로젝트를 빌드합니다.
291
+ * pnpm dev / pnpm dev all 하면 실행되는 함수입니다.
292
+ * 프로젝트에 대해 HMR 지원하는 개발 서버를 띄워줍니다.
224
293
  *
225
- * 빌드에 필요한 .swcrc는 프로젝트 루트에서 찾고, 없으면 sonamu가 관리하는 .swcrc.project-default를 사용합니다.
294
+ * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
295
+ */
296
+ function dev_all() {
297
+ console.log(chalk.yellow.bold("Starting Sonamu dev server...\n"));
298
+ spawnApiDevServer();
299
+ }
300
+
301
+ /**
302
+ * pnpm dev api 하면 실행되는 함수입니다.
303
+ * API 전용 개발 서버를 띄웁니다.
304
+ * dev_all과 거의 동일하되, 통합 웹 서버를 비활성화합니다.
226
305
  *
227
- * 실제 빌드 타겟(아티팩트)과 동작은 build-config.ts에 정의되어 있습니다.
228
- * 이 함수는 build-config.ts에 정의된 동작들을 실행해주는 역할만 합니다.
306
+ * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
307
+ */
308
+ function dev_api() {
309
+ console.log(chalk.yellow.bold("Starting Sonamu API-only dev server...\n"));
310
+ spawnApiDevServer({
311
+ extraEnv: { SONAMU_DISABLE_INTEGRATED_WEB: "yes" },
312
+ });
313
+ }
314
+
315
+ /**
316
+ * pnpm dev web 하면 실행되는 함수입니다.
317
+ * Vite 개발 서버를 단독으로 실행합니다.
318
+ * -- 뒤의 인자는 Vite에 그대로 전달됩니다.
229
319
  *
230
320
  * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
231
321
  */
232
- async function build() {
322
+ async function dev_web() {
233
323
  const appRoot = findAppRootPath();
324
+ const webPath = path.join(appRoot, "web");
325
+
326
+ if (!(await exists(webPath))) {
327
+ console.error(`web 디렉토리를 찾을 수 없습니다: ${webPath}`);
328
+ process.exit(1);
329
+ }
330
+
331
+ // -- 뒤의 인자 추출
332
+ const doubleDashIndex = process.argv.indexOf("--");
333
+ const passthroughArgs = doubleDashIndex !== -1 ? process.argv.slice(doubleDashIndex + 1) : [];
334
+
335
+ const viteArgs = ["exec", "vite", ...passthroughArgs];
336
+
337
+ console.log(chalk.yellow.bold("Starting Vite dev server...\n"));
338
+
339
+ const viteProcess = spawn("pnpm", viteArgs, {
340
+ cwd: webPath,
341
+ stdio: "inherit",
342
+ });
234
343
 
235
- // .swcrc 파일을 지정합니다.
344
+ viteProcess.on("exit", (code) => {
345
+ process.exit(code ?? 0);
346
+ });
347
+
348
+ // SIGINT/SIGTERM 시 Vite 프로세스를 gracefully 종료합니다.
349
+ for (const signal of ["SIGINT", "SIGTERM"] as const) {
350
+ process.on(signal, () => {
351
+ viteProcess.kill(signal);
352
+ });
353
+ }
354
+ }
355
+
356
+ /**
357
+ * SWC 설정 파일 경로를 결정합니다. API 빌드(SWC)에서만 사용됩니다.
358
+ * 프로젝트 루트에 .swcrc가 있으면 그것을, 없으면 sonamu 기본 설정을 사용합니다.
359
+ */
360
+ async function resolveSwcConfigPath(): Promise<string> {
236
361
  let swcFilePath = ".swcrc";
237
362
  try {
238
363
  if (await exists(swcFilePath)) {
239
- // 사용자 프로젝트에 .swcrc가 있으면 우선으로 사용합니다.
240
364
  console.log(chalk.dim("Using .swcrc from project root..."));
241
365
  } else {
242
- // 아니라면 sonamu가 관리하는 .swcrc.project-default를 가져다 씁니다.
243
366
  console.log(chalk.dim("Using default .swcrc from sonamu package..."));
244
367
  swcFilePath = path.join(import.meta.dirname, "..", "..", ".swcrc.project-default");
245
368
  }
@@ -247,8 +370,28 @@ async function build() {
247
370
  console.error(chalk.red("Setting up swc config file failed."), error);
248
371
  process.exit(1);
249
372
  }
373
+ return swcFilePath;
374
+ }
375
+
376
+ /**
377
+ * sonamu build / sonamu build all 하면 실행되는 함수입니다.
378
+ * build_api + build_web의 합성입니다. Web 디렉토리가 없으면 Web 빌드를 스킵합니다.
379
+ */
380
+ async function build_all() {
381
+ await build_api();
382
+ await build_web({ skipIfMissing: true });
383
+ }
384
+
385
+ /**
386
+ * pnpm build api 하면 실행되는 함수입니다.
387
+ * API 프로젝트만 빌드합니다.
388
+ *
389
+ * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
390
+ */
391
+ async function build_api() {
392
+ const appRoot = findAppRootPath();
393
+ const swcFilePath = await resolveSwcConfigPath();
250
394
 
251
- // API 프로젝트를 빌드합니다.
252
395
  const apiStartedAt = Date.now();
253
396
  try {
254
397
  for (const artifact of API_ARTIFACTS) {
@@ -258,12 +401,32 @@ async function build() {
258
401
  await runBuildSteps(artifact, { cwd, buildCommandArgs: { configFilePath: swcFilePath } });
259
402
  }
260
403
  printBuildSummary("API", true, Date.now() - apiStartedAt);
261
- } catch {
404
+ } catch (e) {
262
405
  printBuildSummary("API", false, Date.now() - apiStartedAt);
406
+ console.error(e);
407
+ process.exit(1);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * pnpm build web 하면 실행되는 함수입니다.
413
+ * Web 프로젝트만 빌드합니다.
414
+ *
415
+ * Sonamu.init 없이 호출될 것을 상정하여 구현되었습니다.
416
+ */
417
+ async function build_web({ skipIfMissing = false } = {}) {
418
+ const appRoot = findAppRootPath();
419
+ const webPath = path.join(appRoot, "web");
420
+
421
+ if (!(await exists(webPath))) {
422
+ if (skipIfMissing) {
423
+ console.log(chalk.gray("Web 디렉토리가 없으므로 Web 빌드를 건너뜁니다."));
424
+ return;
425
+ }
426
+ console.error(`web 디렉토리를 찾을 수 없습니다: ${webPath}`);
263
427
  process.exit(1);
264
428
  }
265
429
 
266
- // Web 프로젝트를 빌드합니다.
267
430
  const webStartedAt = Date.now();
268
431
  try {
269
432
  for (const artifact of WEB_ARTIFACTS) {
@@ -273,8 +436,9 @@ async function build() {
273
436
  await runBuildSteps(artifact, { cwd, buildCommandArgs: {} });
274
437
  }
275
438
  printBuildSummary("Web", true, Date.now() - webStartedAt);
276
- } catch {
439
+ } catch (e) {
277
440
  printBuildSummary("Web", false, Date.now() - webStartedAt);
441
+ console.error(e);
278
442
  process.exit(1);
279
443
  }
280
444
  }
@@ -301,9 +465,9 @@ async function runBuildSteps<T>(
301
465
  printTaskStart(step.name, step.cmd, isLast);
302
466
  await execWithLinePrefix(step.cmd, { cwd: options.cwd });
303
467
  printTaskSuccess(step.name, isLast);
304
- } catch {
468
+ } catch (e) {
305
469
  printTaskFailed(step.name, isLast);
306
- throw new Error(`${step.name} failed`);
470
+ throw new Error(`${step.name} failed`, { cause: e });
307
471
  }
308
472
  }
309
473
  }
@@ -323,7 +487,7 @@ async function start() {
323
487
 
324
488
  if (!(await exists(entryPoint))) {
325
489
  console.log(chalk.red(`${entryPoint} not found. Please build your project first.`));
326
- console.log(chalk.blue("Run: yarn sonamu build"));
490
+ console.log(chalk.blue("Run: pnpm sonamu build"));
327
491
  return;
328
492
  }
329
493
 
@@ -470,6 +634,77 @@ async function fixture_sync() {
470
634
  await FixtureManager.sync();
471
635
  }
472
636
 
637
+ /**
638
+ * fixture gen 명령어
639
+ * 옵션을 process.argv에서 파싱
640
+ */
641
+ async function fixture_gen() {
642
+ const options = parseOptions(process.argv);
643
+ await fixtureGenCommand(options);
644
+ }
645
+
646
+ /**
647
+ * fixture fetch 명령어
648
+ * 옵션을 process.argv에서 파싱
649
+ */
650
+ async function fixture_fetch() {
651
+ const options = parseOptions(process.argv);
652
+ await fixtureFetchCommand(options);
653
+ }
654
+
655
+ /**
656
+ * fixture explore 명령어
657
+ * 옵션을 process.argv에서 파싱
658
+ */
659
+ async function fixture_explore() {
660
+ const options = parseOptions(process.argv);
661
+ await fixtureExploreCommand(options);
662
+ }
663
+
664
+ /**
665
+ * 간단한 옵션 파서
666
+ */
667
+ function parseOptions(
668
+ argv: string[],
669
+ ): Record<string, string | boolean | string[]> & { _: string[] } {
670
+ const options: Record<string, string | boolean | string[]> & { _: string[] } = { _: [] };
671
+
672
+ for (let i = 0; i < argv.length; i++) {
673
+ const arg = argv[i];
674
+
675
+ if (arg.startsWith("--")) {
676
+ const key = arg.slice(2);
677
+
678
+ if (key.includes("=")) {
679
+ const [k, v] = key.split("=");
680
+ options[k] = v;
681
+ } else {
682
+ const next = argv[i + 1];
683
+ if (next && !next.startsWith("--")) {
684
+ options[key] = next;
685
+ i++;
686
+ } else {
687
+ options[key] = true;
688
+ }
689
+ }
690
+ } else if (arg.startsWith("-")) {
691
+ const key = arg.slice(1);
692
+ const next = argv[i + 1];
693
+
694
+ if (next && !next.startsWith("-")) {
695
+ options[key] = next;
696
+ i++;
697
+ } else {
698
+ options[key] = true;
699
+ }
700
+ } else {
701
+ options._.push(arg);
702
+ }
703
+ }
704
+
705
+ return options;
706
+ }
707
+
473
708
  async function stub_practice(name: string) {
474
709
  const practiceDir = path.join(Sonamu.apiRootPath, "src", "practices");
475
710
  const fileNames = await readdir(practiceDir);
@@ -523,6 +758,171 @@ async function stub_practice(name: string) {
523
758
 
524
759
  async function stub_entity(entityId: string) {
525
760
  await Sonamu.syncer.createEntity({ entityId, title: entityId });
761
+
762
+ const { flags } = parseCliOptions();
763
+ const useAI = flags.has("ai");
764
+ const noCones = flags.has("no-cones");
765
+
766
+ // --no-cones: cone 생성 스킵
767
+ if (noCones) {
768
+ console.log(`✓ Entity '${entityId}' created without cones`);
769
+ return;
770
+ }
771
+
772
+ const { EntityManager } = await import("../entity/entity-manager");
773
+ const entity = EntityManager.get(entityId);
774
+ if (!entity) {
775
+ console.error(`Entity not found: ${entityId}`);
776
+ return;
777
+ }
778
+
779
+ // --ai: LLM으로 cone 생성
780
+ if (useAI) {
781
+ console.log(`✓ Entity '${entityId}' created`);
782
+ console.log(`🌟 Generating AI-powered cones...`);
783
+ try {
784
+ const configLocale = Sonamu.config.i18n?.defaultLocale;
785
+ const locale =
786
+ configLocale === "ko" || configLocale === "en" || configLocale === "ja"
787
+ ? configLocale
788
+ : "ko";
789
+
790
+ const result = await entity.generateCones({
791
+ preserveExisting: false,
792
+ onlyEmpty: false,
793
+ locale,
794
+ });
795
+
796
+ console.log(`✅ Done (${result.tokensUsed} tokens)`);
797
+ } catch (error) {
798
+ if (error instanceof Error && error.message.includes("ANTHROPIC_API_KEY")) {
799
+ console.error(`\n❌ ${error.message}`);
800
+ console.error(`\n💡 Remove --ai flag to use template cones instead`);
801
+ } else {
802
+ throw error;
803
+ }
804
+ }
805
+ return;
806
+ }
807
+
808
+ // 기본: 템플릿 cone 자동 생성
809
+ // LLM 없이 faker-mappings.ts를 활용하여 기본 cone 메타데이터를 생성합니다.
810
+ // 이를 통해 ANTHROPIC_API_KEY가 없어도 Sonamu를 사용할 수 있으며,
811
+ // 생성된 템플릿 cone은 나중에 'cone gen' 명령어로 AI를 통해 업그레이드할 수 있습니다.
812
+ console.log(`🌟 Generating template cones...`);
813
+ await entity.generateTemplateCones();
814
+ console.log(`✓ Entity '${entityId}' created with template cones`);
815
+ console.log(`💡 Tip: Run 'pnpm sonamu cone gen ${entityId}' to improve with AI`);
816
+ }
817
+
818
+ /**
819
+ * AI를 사용하여 entity의 cone 메타데이터를 생성하거나 업그레이드합니다.
820
+ *
821
+ * 옵션:
822
+ * - --regenerate: 전체 재생성 (기존 cone 덮어쓰기)
823
+ * - --locale <ko|en|ja>: 생성 언어 지정
824
+ * - 기본: onlyEmpty 모드 (기존 fixtureHint 보존)
825
+ *
826
+ * ANTHROPIC_API_KEY 필요 (sonamu.secret.ts 또는 환경변수)
827
+ */
828
+ async function cone_gen(entityId: string) {
829
+ const { EntityManager } = await import("../entity/entity-manager");
830
+ const { flags, options } = parseCliOptions();
831
+
832
+ // --all 옵션: 모든 entity cone 생성
833
+ if (flags.has("all") || entityId === "all") {
834
+ const allEntities = EntityManager.getAllEntities();
835
+ console.log(`🌟 Generating AI-powered cones for ${allEntities.length} entities...\n`);
836
+
837
+ let totalTokens = 0;
838
+ const errors: string[] = [];
839
+
840
+ for (const entity of allEntities) {
841
+ try {
842
+ console.log(`Processing ${entity.id}...`);
843
+ const configLocale = options.locale || Sonamu.config.i18n?.defaultLocale;
844
+ const locale =
845
+ configLocale === "ko" || configLocale === "en" || configLocale === "ja"
846
+ ? configLocale
847
+ : "ko";
848
+
849
+ const result = await entity.generateCones({
850
+ preserveExisting: !flags.has("regenerate"),
851
+ onlyEmpty: !flags.has("regenerate"),
852
+ locale,
853
+ });
854
+
855
+ totalTokens += result.tokensUsed;
856
+ console.log(` ✓ ${entity.id} (${result.tokensUsed} tokens)\n`);
857
+ } catch (error) {
858
+ const message = error instanceof Error ? error.message : "Unknown error";
859
+ errors.push(`${entity.id}: ${message}`);
860
+ console.error(` ✗ ${entity.id}: ${message}\n`);
861
+ }
862
+ }
863
+
864
+ console.log(`\n✅ Done! Total: ${totalTokens} tokens used`);
865
+ const estimatedCost = (totalTokens * 9) / 1_000_000;
866
+ console.log(`💰 Estimated cost: ~$${estimatedCost.toFixed(4)}`);
867
+
868
+ if (errors.length > 0) {
869
+ console.error(`\n❌ Failed entities (${errors.length}):`);
870
+ for (const err of errors) {
871
+ console.error(` - ${err}`);
872
+ }
873
+ }
874
+ return;
875
+ }
876
+
877
+ // 단일 entity cone 생성
878
+ const entity = EntityManager.get(entityId);
879
+ if (!entity) {
880
+ console.error(`Entity not found: ${entityId}`);
881
+ return;
882
+ }
883
+
884
+ const mode = flags.has("regenerate") ? "regenerating" : "generating";
885
+ console.log(
886
+ `🌟 ${mode === "regenerating" ? "Regenerating" : "Generating"} AI-powered cones for ${entityId}...`,
887
+ );
888
+
889
+ try {
890
+ const configLocale = options.locale || Sonamu.config.i18n?.defaultLocale;
891
+ const locale =
892
+ configLocale === "ko" || configLocale === "en" || configLocale === "ja" ? configLocale : "ko";
893
+
894
+ const result = await entity.generateCones({
895
+ preserveExisting: !flags.has("regenerate"),
896
+ onlyEmpty: !flags.has("regenerate"),
897
+ locale,
898
+ });
899
+
900
+ console.log(`✅ Done! (${result.tokensUsed} tokens used)`);
901
+
902
+ // 토큰 비용 계산 (대략적인 추정)
903
+ // Claude Sonnet 4.5: input $3/M tokens, output $15/M tokens
904
+ // 간단하게 평균 $9/M tokens로 계산
905
+ const estimatedCost = (result.tokensUsed * 9) / 1_000_000;
906
+ console.log(`💰 Estimated cost: ~$${estimatedCost.toFixed(4)}`);
907
+ } catch (error) {
908
+ if (error instanceof Error) {
909
+ if (error.message.includes("ANTHROPIC_API_KEY")) {
910
+ console.error(`\n❌ ${error.message}`);
911
+ console.error(`\n💡 To use AI-powered cone generation:`);
912
+ console.error(` 1. Get an API key from https://console.anthropic.com/`);
913
+ console.error(
914
+ ` 2. Add it to sonamu.secret.ts or set ANTHROPIC_API_KEY environment variable`,
915
+ );
916
+ } else if (error.message.includes("Rate limit")) {
917
+ console.error(`\n❌ ${error.message}`);
918
+ console.error(`\n💡 Please wait a moment and try again.`);
919
+ } else {
920
+ console.error(`\n❌ Failed to generate cones: ${error.message}`);
921
+ }
922
+ } else {
923
+ console.error(`\n❌ Failed to generate cones: Unknown error`);
924
+ }
925
+ }
526
926
  }
527
927
 
528
928
  async function scaffold_model(entityId: string) {
@@ -590,6 +990,27 @@ async function skills_sync() {
590
990
  }
591
991
  }
592
992
 
993
+ // project 디렉토리 초기화 (없으면 생성, 있으면 유지)
994
+ const sourceProjectDir = path.join(sourceBase, "project");
995
+ const targetProjectDir = path.join(claudeDir, "skills", "project");
996
+
997
+ if (await exists(sourceProjectDir)) {
998
+ if (!(await exists(targetProjectDir))) {
999
+ try {
1000
+ await cp(sourceProjectDir, targetProjectDir, { recursive: true });
1001
+ console.log(chalk.green(`✓ Project templates initialized`));
1002
+ } catch (error) {
1003
+ console.error(
1004
+ chalk.red(
1005
+ `✗ Failed to initialize project templates: ${error instanceof Error ? error.message : String(error)}`,
1006
+ ),
1007
+ );
1008
+ }
1009
+ } else {
1010
+ console.log(chalk.dim(`⏭ Project templates already exist (preserved)`));
1011
+ }
1012
+ }
1013
+
593
1014
  // CLAUDE.md 복사/업데이트
594
1015
  if (await exists(sourceClaudeMd)) {
595
1016
  try {