@tailor-platform/erp-kit 0.2.1 → 0.3.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 (633) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +154 -80
  3. package/dist/cli.mjs +1742 -0
  4. package/package.json +16 -14
  5. package/schemas/app-compose/story.yml +12 -0
  6. package/schemas/module/command.yml +9 -0
  7. package/schemas/module/module.yml +4 -0
  8. package/schemas/module/query.yml +9 -0
  9. package/skills/erp-kit-app-1-requirements/SKILL.md +22 -11
  10. package/skills/erp-kit-app-2-requirements-review/SKILL.md +103 -0
  11. package/skills/erp-kit-app-2-requirements-review/references/best-practices-check.md +71 -0
  12. package/skills/erp-kit-app-2-requirements-review/references/boundary-consistency-check.md +74 -0
  13. package/skills/erp-kit-app-2-requirements-review/references/requirements-report-format.md +25 -0
  14. package/skills/erp-kit-app-3-plan/SKILL.md +154 -0
  15. package/skills/erp-kit-app-3-plan/references/resolver-extraction.md +89 -0
  16. package/skills/erp-kit-app-3-plan/references/screen-extraction.md +74 -0
  17. package/skills/erp-kit-app-3-plan/references/story-extraction.md +86 -0
  18. package/skills/erp-kit-app-4-plan-review/SKILL.md +168 -0
  19. package/skills/erp-kit-app-4-plan-review/references/actor-flow-parity.md +73 -0
  20. package/skills/erp-kit-app-4-plan-review/references/business-flow-story-parity.md +86 -0
  21. package/skills/erp-kit-app-4-plan-review/references/orphan-detection.md +69 -0
  22. package/skills/erp-kit-app-4-plan-review/references/parity-report-format.md +52 -0
  23. package/skills/erp-kit-app-4-plan-review/references/story-resolver-parity.md +83 -0
  24. package/skills/erp-kit-app-4-plan-review/references/story-screen-parity.md +73 -0
  25. package/skills/erp-kit-app-5-impl-backend/SKILL.md +98 -0
  26. package/skills/erp-kit-app-5-impl-backend/references/app-config.md +38 -0
  27. package/skills/erp-kit-app-5-impl-backend/references/module-wiring.md +48 -0
  28. package/skills/erp-kit-app-5-impl-backend/references/resolver-patterns.md +68 -0
  29. package/skills/erp-kit-app-6-impl-frontend/SKILL.md +74 -0
  30. package/skills/{erp-kit-app-5-implementation/references/frontend.md → erp-kit-app-6-impl-frontend/references/pages.md} +8 -90
  31. package/skills/erp-kit-app-7-impl-review/SKILL.md +176 -0
  32. package/skills/erp-kit-app-7-impl-review/references/impl-parity-report-format.md +52 -0
  33. package/skills/erp-kit-app-7-impl-review/references/module-wiring-parity.md +84 -0
  34. package/skills/erp-kit-app-7-impl-review/references/resolver-doc-code-parity.md +86 -0
  35. package/skills/erp-kit-app-7-impl-review/references/screen-doc-code-parity.md +86 -0
  36. package/skills/erp-kit-app-shared/SKILL.md +15 -0
  37. package/skills/erp-kit-app-shared/references/link-format-reference.md +13 -0
  38. package/skills/erp-kit-app-shared/references/naming-conventions.md +21 -0
  39. package/skills/erp-kit-app-shared/references/resolver-classification.md +23 -0
  40. package/skills/erp-kit-app-shared/references/schema-constraints.md +25 -0
  41. package/skills/erp-kit-module-1-requirements/SKILL.md +126 -0
  42. package/skills/erp-kit-module-1-requirements/references/boundary-analysis.md +51 -0
  43. package/skills/erp-kit-module-1-requirements/references/erp-research.md +57 -0
  44. package/skills/erp-kit-module-1-requirements/references/feature-doc.md +61 -0
  45. package/skills/erp-kit-module-2-requirements-review/SKILL.md +112 -0
  46. package/skills/erp-kit-module-2-requirements-review/references/best-practices-check.md +79 -0
  47. package/skills/erp-kit-module-2-requirements-review/references/boundary-consistency-check.md +70 -0
  48. package/skills/erp-kit-module-2-requirements-review/references/requirements-report-format.md +25 -0
  49. package/skills/erp-kit-module-3-plan/SKILL.md +107 -0
  50. package/skills/erp-kit-module-3-plan/references/command-extraction.md +87 -0
  51. package/skills/erp-kit-module-3-plan/references/model-extraction.md +72 -0
  52. package/skills/{erp-kit-module-2-feature-breakdown → erp-kit-module-3-plan}/references/naming.md +15 -1
  53. package/skills/erp-kit-module-3-plan/references/query-extraction.md +59 -0
  54. package/skills/erp-kit-module-4-plan-review/SKILL.md +158 -0
  55. package/skills/erp-kit-module-4-plan-review/references/command-model-consistency.md +46 -0
  56. package/skills/erp-kit-module-4-plan-review/references/feature-command-parity.md +97 -0
  57. package/skills/erp-kit-module-4-plan-review/references/feature-model-parity.md +47 -0
  58. package/skills/erp-kit-module-4-plan-review/references/feature-query-parity.md +70 -0
  59. package/skills/erp-kit-module-4-plan-review/references/parity-report-format.md +52 -0
  60. package/skills/erp-kit-module-5-impl/SKILL.md +120 -0
  61. package/skills/erp-kit-module-5-impl/references/command-impl.md +68 -0
  62. package/skills/erp-kit-module-5-impl/references/exports.md +10 -0
  63. package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/generated-code.md +2 -2
  64. package/skills/erp-kit-module-5-impl/references/model-impl.md +45 -0
  65. package/skills/erp-kit-module-5-impl/references/query-impl.md +53 -0
  66. package/skills/erp-kit-module-6-impl-review/SKILL.md +187 -0
  67. package/skills/erp-kit-module-6-impl-review/references/command-doc-code-parity.md +92 -0
  68. package/skills/erp-kit-module-6-impl-review/references/command-doc-test-parity.md +93 -0
  69. package/skills/erp-kit-module-6-impl-review/references/error-implementation-parity.md +95 -0
  70. package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/errors.md +2 -2
  71. package/skills/erp-kit-module-6-impl-review/references/impl-parity-report-format.md +52 -0
  72. package/skills/erp-kit-module-6-impl-review/references/model-doc-code-parity.md +80 -0
  73. package/skills/erp-kit-module-shared/SKILL.md +1 -1
  74. package/skills/erp-kit-module-shared/references/commands.md +1 -1
  75. package/skills/erp-kit-module-shared/references/errors.md +13 -10
  76. package/skills/erp-kit-module-shared/references/queries.md +110 -37
  77. package/skills/erp-kit-module-shared/references/structure.md +1 -1
  78. package/skills/erp-kit-module-shared/references/testing.md +10 -0
  79. package/skills/erp-kit-update/SKILL.md +4 -4
  80. package/src/app.ts +1 -1
  81. package/src/commands/app/index.ts +57 -24
  82. package/src/commands/check.ts +1 -1
  83. package/src/commands/generate-doc.test.ts +63 -0
  84. package/src/commands/generate-doc.ts +98 -0
  85. package/src/commands/index.ts +16 -5
  86. package/src/commands/init-module.test.ts +43 -0
  87. package/src/commands/init-module.ts +74 -0
  88. package/src/commands/init.test.ts +22 -69
  89. package/src/commands/init.ts +28 -115
  90. package/src/commands/lib/distribute.test.ts +126 -0
  91. package/src/commands/lib/distribute.ts +129 -0
  92. package/src/commands/module/generate.ts +33 -13
  93. package/src/commands/module/index.ts +18 -28
  94. package/src/commands/parse-doc-test-cases.ts +55 -0
  95. package/src/commands/sync-check.test.ts +173 -0
  96. package/src/commands/sync-check.ts +103 -2
  97. package/src/commands/update.test.ts +87 -0
  98. package/src/commands/update.ts +41 -0
  99. package/src/generator/generate-code-boilerplate.test.ts +142 -0
  100. package/src/generator/generate-code.test.ts +47 -12
  101. package/src/generator/generate-code.ts +123 -20
  102. package/src/integration.test.ts +3 -3
  103. package/src/module.ts +14 -97
  104. package/src/modules/item-management/README.md +8 -0
  105. package/src/modules/item-management/command/activateItem.generated.ts +1 -1
  106. package/src/modules/item-management/command/activateItem.test.ts +12 -18
  107. package/src/modules/item-management/command/activateItem.ts +9 -5
  108. package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +1 -1
  109. package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +10 -24
  110. package/src/modules/item-management/command/assignItemToTaxonomy.ts +19 -16
  111. package/src/modules/item-management/command/createItem.generated.ts +1 -1
  112. package/src/modules/item-management/command/createItem.test.ts +11 -11
  113. package/src/modules/item-management/command/createItem.ts +16 -7
  114. package/src/modules/item-management/command/createTaxonomyNode.generated.ts +1 -1
  115. package/src/modules/item-management/command/createTaxonomyNode.test.ts +9 -9
  116. package/src/modules/item-management/command/createTaxonomyNode.ts +33 -14
  117. package/src/modules/item-management/command/deactivateItem.generated.ts +1 -1
  118. package/src/modules/item-management/command/deactivateItem.test.ts +12 -18
  119. package/src/modules/item-management/command/deactivateItem.ts +9 -5
  120. package/src/modules/item-management/command/deleteItem.generated.ts +1 -1
  121. package/src/modules/item-management/command/deleteItem.test.ts +10 -16
  122. package/src/modules/item-management/command/deleteItem.ts +9 -5
  123. package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +1 -1
  124. package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +10 -16
  125. package/src/modules/item-management/command/deleteTaxonomyNode.ts +22 -12
  126. package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +1 -1
  127. package/src/modules/item-management/command/moveTaxonomyNode.test.ts +10 -10
  128. package/src/modules/item-management/command/moveTaxonomyNode.ts +63 -19
  129. package/src/modules/item-management/command/reactivateItem.generated.ts +1 -1
  130. package/src/modules/item-management/command/reactivateItem.test.ts +12 -18
  131. package/src/modules/item-management/command/reactivateItem.ts +9 -5
  132. package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +1 -1
  133. package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +9 -16
  134. package/src/modules/item-management/command/removeItemFromTaxonomy.ts +11 -6
  135. package/src/modules/item-management/command/updateItem.generated.ts +1 -1
  136. package/src/modules/item-management/command/updateItem.test.ts +16 -16
  137. package/src/modules/item-management/command/updateItem.ts +11 -6
  138. package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +1 -1
  139. package/src/modules/item-management/command/updateTaxonomyNode.test.ts +14 -20
  140. package/src/modules/item-management/command/updateTaxonomyNode.ts +9 -6
  141. package/src/modules/item-management/docs/commands/ActivateItem.md +8 -0
  142. package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +7 -0
  143. package/src/modules/item-management/docs/commands/CreateItem.md +10 -0
  144. package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +9 -0
  145. package/src/modules/item-management/docs/commands/DeactivateItem.md +8 -0
  146. package/src/modules/item-management/docs/commands/DeleteItem.md +7 -0
  147. package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +7 -0
  148. package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +10 -0
  149. package/src/modules/item-management/docs/commands/ReactivateItem.md +8 -0
  150. package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +5 -0
  151. package/src/modules/item-management/docs/commands/UpdateItem.md +15 -0
  152. package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +9 -0
  153. package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +8 -0
  154. package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +7 -0
  155. package/src/modules/item-management/docs/queries/DetectCircularReference.md +9 -0
  156. package/src/modules/item-management/docs/queries/GetItem.md +9 -0
  157. package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +5 -0
  158. package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +7 -0
  159. package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +5 -0
  160. package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +6 -0
  161. package/src/modules/item-management/index.ts +0 -51
  162. package/src/modules/item-management/lib/errors.generated.ts +24 -24
  163. package/src/modules/item-management/lib/permissions.generated.ts +1 -1
  164. package/src/modules/item-management/lib/types.ts +1 -1
  165. package/src/modules/item-management/module.ts +1 -1
  166. package/src/modules/item-management/query/calculateNodeDepth.generated.ts +1 -1
  167. package/src/modules/item-management/query/calculateNodeDepth.test.ts +21 -6
  168. package/src/modules/item-management/query/calculateNodeDepth.ts +2 -2
  169. package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +1 -1
  170. package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +17 -5
  171. package/src/modules/item-management/query/calculateSubtreeDepth.ts +2 -2
  172. package/src/modules/item-management/query/detectCircularReference.generated.ts +1 -1
  173. package/src/modules/item-management/query/detectCircularReference.test.ts +25 -7
  174. package/src/modules/item-management/query/detectCircularReference.ts +4 -4
  175. package/src/modules/item-management/query/getItem.generated.ts +1 -1
  176. package/src/modules/item-management/query/getItem.test.ts +25 -7
  177. package/src/modules/item-management/query/getItem.ts +2 -2
  178. package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +1 -1
  179. package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +9 -3
  180. package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +2 -2
  181. package/src/modules/item-management/query/getTaxonomyNode.generated.ts +1 -1
  182. package/src/modules/item-management/query/getTaxonomyNode.test.ts +17 -5
  183. package/src/modules/item-management/query/getTaxonomyNode.ts +2 -2
  184. package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +1 -1
  185. package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +9 -3
  186. package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +2 -2
  187. package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +1 -1
  188. package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +13 -4
  189. package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +2 -2
  190. package/src/modules/item-management/tailor.config.ts +6 -4
  191. package/src/modules/item-management/tailor.d.ts +13 -0
  192. package/src/modules/primitives/README.md +8 -0
  193. package/src/modules/primitives/command/activateCategory.generated.ts +1 -1
  194. package/src/modules/primitives/command/activateCategory.test.ts +8 -18
  195. package/src/modules/primitives/command/activateCategory.ts +9 -5
  196. package/src/modules/primitives/command/activateCurrency.generated.ts +1 -1
  197. package/src/modules/primitives/command/activateCurrency.test.ts +8 -18
  198. package/src/modules/primitives/command/activateCurrency.ts +9 -5
  199. package/src/modules/primitives/command/activateUnit.generated.ts +1 -1
  200. package/src/modules/primitives/command/activateUnit.test.ts +8 -15
  201. package/src/modules/primitives/command/activateUnit.ts +9 -5
  202. package/src/modules/primitives/command/createCategory.generated.ts +1 -1
  203. package/src/modules/primitives/command/createCategory.test.ts +29 -44
  204. package/src/modules/primitives/command/createCategory.ts +9 -5
  205. package/src/modules/primitives/command/createCurrency.generated.ts +1 -1
  206. package/src/modules/primitives/command/createCurrency.test.ts +53 -78
  207. package/src/modules/primitives/command/createCurrency.ts +9 -6
  208. package/src/modules/primitives/command/createExchangeRate.generated.ts +1 -1
  209. package/src/modules/primitives/command/createExchangeRate.test.ts +59 -97
  210. package/src/modules/primitives/command/createExchangeRate.ts +13 -7
  211. package/src/modules/primitives/command/createUnit.generated.ts +1 -1
  212. package/src/modules/primitives/command/createUnit.test.ts +59 -90
  213. package/src/modules/primitives/command/createUnit.ts +9 -6
  214. package/src/modules/primitives/command/deactivateCategory.generated.ts +1 -1
  215. package/src/modules/primitives/command/deactivateCategory.test.ts +15 -33
  216. package/src/modules/primitives/command/deactivateCategory.ts +9 -5
  217. package/src/modules/primitives/command/deactivateCurrency.generated.ts +1 -1
  218. package/src/modules/primitives/command/deactivateCurrency.test.ts +12 -26
  219. package/src/modules/primitives/command/deactivateCurrency.ts +9 -5
  220. package/src/modules/primitives/command/deactivateUnit.generated.ts +1 -1
  221. package/src/modules/primitives/command/deactivateUnit.test.ts +15 -30
  222. package/src/modules/primitives/command/deactivateUnit.ts +14 -7
  223. package/src/modules/primitives/command/setBaseCurrency.generated.ts +1 -1
  224. package/src/modules/primitives/command/setBaseCurrency.test.ts +18 -40
  225. package/src/modules/primitives/command/setBaseCurrency.ts +15 -7
  226. package/src/modules/primitives/command/setReferenceUnit.generated.ts +1 -1
  227. package/src/modules/primitives/command/setReferenceUnit.test.ts +22 -44
  228. package/src/modules/primitives/command/setReferenceUnit.ts +21 -9
  229. package/src/modules/primitives/docs/commands/ActivateCategory.md +6 -0
  230. package/src/modules/primitives/docs/commands/ActivateCurrency.md +6 -0
  231. package/src/modules/primitives/docs/commands/ActivateUnit.md +6 -0
  232. package/src/modules/primitives/docs/commands/CreateCategory.md +6 -0
  233. package/src/modules/primitives/docs/commands/CreateCurrency.md +10 -0
  234. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +11 -0
  235. package/src/modules/primitives/docs/commands/CreateUnit.md +10 -0
  236. package/src/modules/primitives/docs/commands/DeactivateCategory.md +7 -0
  237. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +7 -0
  238. package/src/modules/primitives/docs/commands/DeactivateUnit.md +7 -0
  239. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +7 -0
  240. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +7 -0
  241. package/src/modules/primitives/docs/queries/ConvertAmount.md +14 -0
  242. package/src/modules/primitives/docs/queries/ConvertQuantity.md +13 -0
  243. package/src/modules/primitives/docs/queries/GetBaseCurrency.md +5 -0
  244. package/src/modules/primitives/docs/queries/GetCurrency.md +7 -0
  245. package/src/modules/primitives/docs/queries/GetUnit.md +7 -0
  246. package/src/modules/primitives/docs/queries/GetUoMCategory.md +7 -0
  247. package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +15 -5
  248. package/src/modules/primitives/index.ts +0 -49
  249. package/src/modules/primitives/lib/errors.generated.ts +23 -23
  250. package/src/modules/primitives/lib/permissions.generated.ts +1 -1
  251. package/src/modules/primitives/lib/types.ts +1 -1
  252. package/src/modules/primitives/module.ts +1 -1
  253. package/src/modules/primitives/query/convertAmount.generated.ts +1 -1
  254. package/src/modules/primitives/query/convertAmount.test.ts +110 -77
  255. package/src/modules/primitives/query/convertAmount.ts +61 -47
  256. package/src/modules/primitives/query/convertQuantity.generated.ts +1 -1
  257. package/src/modules/primitives/query/convertQuantity.test.ts +99 -69
  258. package/src/modules/primitives/query/convertQuantity.ts +12 -10
  259. package/src/modules/primitives/query/getBaseCurrency.generated.ts +1 -1
  260. package/src/modules/primitives/query/getBaseCurrency.test.ts +10 -4
  261. package/src/modules/primitives/query/getBaseCurrency.ts +2 -2
  262. package/src/modules/primitives/query/getCurrency.generated.ts +1 -1
  263. package/src/modules/primitives/query/getCurrency.test.ts +17 -5
  264. package/src/modules/primitives/query/getCurrency.ts +2 -2
  265. package/src/modules/primitives/query/getUnit.generated.ts +1 -1
  266. package/src/modules/primitives/query/getUnit.test.ts +17 -5
  267. package/src/modules/primitives/query/getUnit.ts +2 -2
  268. package/src/modules/primitives/query/getUoMCategory.generated.ts +1 -1
  269. package/src/modules/primitives/query/getUoMCategory.test.ts +17 -5
  270. package/src/modules/primitives/query/getUoMCategory.ts +2 -2
  271. package/src/modules/primitives/query/listUnitsByCategory.generated.ts +1 -1
  272. package/src/modules/primitives/query/listUnitsByCategory.test.ts +80 -0
  273. package/src/modules/primitives/query/listUnitsByCategory.ts +19 -3
  274. package/src/modules/primitives/tailor.config.ts +6 -4
  275. package/src/modules/primitives/tailor.d.ts +13 -0
  276. package/src/modules/product-management/README.md +52 -0
  277. package/src/modules/product-management/command/activateProduct.generated.ts +6 -0
  278. package/src/modules/product-management/command/activateProduct.test.ts +40 -0
  279. package/src/modules/product-management/command/activateProduct.ts +42 -0
  280. package/src/modules/product-management/command/assignProductToCategory.generated.ts +6 -0
  281. package/src/modules/product-management/command/assignProductToCategory.test.ts +90 -0
  282. package/src/modules/product-management/command/assignProductToCategory.ts +62 -0
  283. package/src/modules/product-management/command/createProduct.generated.ts +6 -0
  284. package/src/modules/product-management/command/createProduct.test.ts +149 -0
  285. package/src/modules/product-management/command/createProduct.ts +73 -0
  286. package/src/modules/product-management/command/createProductAttribute.generated.ts +6 -0
  287. package/src/modules/product-management/command/createProductAttribute.test.ts +70 -0
  288. package/src/modules/product-management/command/createProductAttribute.ts +53 -0
  289. package/src/modules/product-management/command/createProductAttributeValue.generated.ts +6 -0
  290. package/src/modules/product-management/command/createProductAttributeValue.test.ts +68 -0
  291. package/src/modules/product-management/command/createProductAttributeValue.ts +63 -0
  292. package/src/modules/product-management/command/createProductCategory.generated.ts +6 -0
  293. package/src/modules/product-management/command/createProductCategory.test.ts +135 -0
  294. package/src/modules/product-management/command/createProductCategory.ts +82 -0
  295. package/src/modules/product-management/command/deactivateProduct.generated.ts +6 -0
  296. package/src/modules/product-management/command/deactivateProduct.test.ts +40 -0
  297. package/src/modules/product-management/command/deactivateProduct.ts +42 -0
  298. package/src/modules/product-management/command/deleteProduct.generated.ts +6 -0
  299. package/src/modules/product-management/command/deleteProduct.test.ts +42 -0
  300. package/src/modules/product-management/command/deleteProduct.ts +42 -0
  301. package/src/modules/product-management/command/deleteProductAttribute.generated.ts +6 -0
  302. package/src/modules/product-management/command/deleteProductAttribute.test.ts +49 -0
  303. package/src/modules/product-management/command/deleteProductAttribute.ts +45 -0
  304. package/src/modules/product-management/command/deleteProductAttributeValue.generated.ts +6 -0
  305. package/src/modules/product-management/command/deleteProductAttributeValue.test.ts +71 -0
  306. package/src/modules/product-management/command/deleteProductAttributeValue.ts +68 -0
  307. package/src/modules/product-management/command/deleteProductCategory.generated.ts +6 -0
  308. package/src/modules/product-management/command/deleteProductCategory.test.ts +74 -0
  309. package/src/modules/product-management/command/deleteProductCategory.ts +53 -0
  310. package/src/modules/product-management/command/generateVariants.generated.ts +6 -0
  311. package/src/modules/product-management/command/generateVariants.test.ts +365 -0
  312. package/src/modules/product-management/command/generateVariants.ts +168 -0
  313. package/src/modules/product-management/command/moveProductCategory.generated.ts +6 -0
  314. package/src/modules/product-management/command/moveProductCategory.test.ts +170 -0
  315. package/src/modules/product-management/command/moveProductCategory.ts +124 -0
  316. package/src/modules/product-management/command/reactivateProduct.generated.ts +6 -0
  317. package/src/modules/product-management/command/reactivateProduct.test.ts +40 -0
  318. package/src/modules/product-management/command/reactivateProduct.ts +42 -0
  319. package/src/modules/product-management/command/removeProductFromCategory.generated.ts +6 -0
  320. package/src/modules/product-management/command/removeProductFromCategory.test.ts +42 -0
  321. package/src/modules/product-management/command/removeProductFromCategory.ts +32 -0
  322. package/src/modules/product-management/command/setProductAttributeAssignment.generated.ts +6 -0
  323. package/src/modules/product-management/command/setProductAttributeAssignment.test.ts +206 -0
  324. package/src/modules/product-management/command/setProductAttributeAssignment.ts +102 -0
  325. package/src/modules/product-management/command/updateProduct.generated.ts +6 -0
  326. package/src/modules/product-management/command/updateProduct.test.ts +168 -0
  327. package/src/modules/product-management/command/updateProduct.ts +95 -0
  328. package/src/modules/product-management/command/updateProductAttribute.generated.ts +6 -0
  329. package/src/modules/product-management/command/updateProductAttribute.test.ts +101 -0
  330. package/src/modules/product-management/command/updateProductAttribute.ts +68 -0
  331. package/src/modules/product-management/command/updateProductAttributeValue.generated.ts +6 -0
  332. package/src/modules/product-management/command/updateProductAttributeValue.test.ts +80 -0
  333. package/src/modules/product-management/command/updateProductAttributeValue.ts +58 -0
  334. package/src/modules/product-management/command/updateProductCategory.generated.ts +6 -0
  335. package/src/modules/product-management/command/updateProductCategory.test.ts +80 -0
  336. package/src/modules/product-management/command/updateProductCategory.ts +66 -0
  337. package/src/modules/product-management/db/product.ts +47 -0
  338. package/src/modules/product-management/db/productAttribute.ts +26 -0
  339. package/src/modules/product-management/db/productAttributeAssignment.ts +58 -0
  340. package/src/modules/product-management/db/productAttributeValue.ts +39 -0
  341. package/src/modules/product-management/db/productCategory.ts +34 -0
  342. package/src/modules/product-management/db/productCategoryAssignment.ts +49 -0
  343. package/src/modules/product-management/db/productVariant.ts +52 -0
  344. package/src/modules/product-management/docs/commands/ActivateProduct.md +39 -0
  345. package/src/modules/product-management/docs/commands/AssignProductToCategory.md +43 -0
  346. package/src/modules/product-management/docs/commands/CreateProduct.md +48 -0
  347. package/src/modules/product-management/docs/commands/CreateProductAttribute.md +39 -0
  348. package/src/modules/product-management/docs/commands/CreateProductAttributeValue.md +42 -0
  349. package/src/modules/product-management/docs/commands/CreateProductCategory.md +54 -0
  350. package/src/modules/product-management/docs/commands/DeactivateProduct.md +39 -0
  351. package/src/modules/product-management/docs/commands/DeleteProduct.md +42 -0
  352. package/src/modules/product-management/docs/commands/DeleteProductAttribute.md +39 -0
  353. package/src/modules/product-management/docs/commands/DeleteProductAttributeValue.md +42 -0
  354. package/src/modules/product-management/docs/commands/DeleteProductCategory.md +43 -0
  355. package/src/modules/product-management/docs/commands/GenerateVariants.md +68 -0
  356. package/src/modules/product-management/docs/commands/MoveProductCategory.md +54 -0
  357. package/src/modules/product-management/docs/commands/ReactivateProduct.md +38 -0
  358. package/src/modules/product-management/docs/commands/RemoveProductFromCategory.md +34 -0
  359. package/src/modules/product-management/docs/commands/SetProductAttributeAssignment.md +62 -0
  360. package/src/modules/product-management/docs/commands/UpdateProduct.md +61 -0
  361. package/src/modules/product-management/docs/commands/UpdateProductAttribute.md +46 -0
  362. package/src/modules/product-management/docs/commands/UpdateProductAttributeValue.md +47 -0
  363. package/src/modules/product-management/docs/commands/UpdateProductCategory.md +46 -0
  364. package/src/modules/product-management/docs/features/attribute-management.md +48 -0
  365. package/src/modules/product-management/docs/features/product-category.md +71 -0
  366. package/src/modules/product-management/docs/features/product-lifecycle.md +66 -0
  367. package/src/modules/product-management/docs/features/variant-generation.md +77 -0
  368. package/src/modules/product-management/docs/models/Product.md +58 -0
  369. package/src/modules/product-management/docs/models/ProductAttribute.md +37 -0
  370. package/src/modules/product-management/docs/models/ProductAttributeAssignment.md +41 -0
  371. package/src/modules/product-management/docs/models/ProductAttributeValue.md +40 -0
  372. package/src/modules/product-management/docs/models/ProductCategory.md +46 -0
  373. package/src/modules/product-management/docs/models/ProductCategoryAssignment.md +37 -0
  374. package/src/modules/product-management/docs/models/ProductVariant.md +41 -0
  375. package/src/modules/product-management/docs/queries/CalculateCategoryDepth.md +47 -0
  376. package/src/modules/product-management/docs/queries/DetectCategoryCircularReference.md +51 -0
  377. package/src/modules/product-management/docs/queries/GetProduct.md +42 -0
  378. package/src/modules/product-management/docs/queries/GetProductAttribute.md +42 -0
  379. package/src/modules/product-management/docs/queries/GetProductAttributeAssignment.md +34 -0
  380. package/src/modules/product-management/docs/queries/GetProductAttributeValue.md +40 -0
  381. package/src/modules/product-management/docs/queries/GetProductCategory.md +42 -0
  382. package/src/modules/product-management/docs/queries/GetProductCategoryAssignment.md +34 -0
  383. package/src/modules/product-management/docs/queries/GetProductVariant.md +41 -0
  384. package/src/modules/product-management/docs/queries/ListAttributeAssignmentsByAttribute.md +34 -0
  385. package/src/modules/product-management/docs/queries/ListCategoryAssignmentsByProduct.md +35 -0
  386. package/src/modules/product-management/docs/queries/ListProductAttributeAssignments.md +34 -0
  387. package/src/modules/product-management/docs/queries/ListProductAttributeValues.md +36 -0
  388. package/src/modules/product-management/docs/queries/ListProductCategoryAssignments.md +34 -0
  389. package/src/modules/product-management/docs/queries/ListProductCategoryChildren.md +34 -0
  390. package/src/modules/product-management/docs/queries/ListProductVariants.md +34 -0
  391. package/src/modules/product-management/generated/enums.ts +9 -0
  392. package/src/modules/product-management/generated/kysely-tailordb.ts +100 -0
  393. package/src/modules/product-management/index.ts +2 -0
  394. package/src/modules/product-management/lib/_db_deps.ts +17 -0
  395. package/src/modules/product-management/lib/errors.generated.ts +152 -0
  396. package/src/modules/product-management/lib/permissions.generated.ts +25 -0
  397. package/src/modules/product-management/lib/types.ts +51 -0
  398. package/src/modules/product-management/module.ts +201 -0
  399. package/src/modules/product-management/query/calculateCategoryDepth.generated.ts +5 -0
  400. package/src/modules/product-management/query/calculateCategoryDepth.test.ts +72 -0
  401. package/src/modules/product-management/query/calculateCategoryDepth.ts +37 -0
  402. package/src/modules/product-management/query/detectCategoryCircularReference.generated.ts +5 -0
  403. package/src/modules/product-management/query/detectCategoryCircularReference.test.ts +72 -0
  404. package/src/modules/product-management/query/detectCategoryCircularReference.ts +44 -0
  405. package/src/modules/product-management/query/getProduct.generated.ts +5 -0
  406. package/src/modules/product-management/query/getProduct.test.ts +59 -0
  407. package/src/modules/product-management/query/getProduct.ts +18 -0
  408. package/src/modules/product-management/query/getProductAttribute.generated.ts +5 -0
  409. package/src/modules/product-management/query/getProductAttribute.test.ts +59 -0
  410. package/src/modules/product-management/query/getProductAttribute.ts +18 -0
  411. package/src/modules/product-management/query/getProductAttributeAssignment.generated.ts +5 -0
  412. package/src/modules/product-management/query/getProductAttributeAssignment.test.ts +37 -0
  413. package/src/modules/product-management/query/getProductAttributeAssignment.ts +18 -0
  414. package/src/modules/product-management/query/getProductAttributeValue.generated.ts +5 -0
  415. package/src/modules/product-management/query/getProductAttributeValue.test.ts +31 -0
  416. package/src/modules/product-management/query/getProductAttributeValue.ts +16 -0
  417. package/src/modules/product-management/query/getProductCategory.generated.ts +5 -0
  418. package/src/modules/product-management/query/getProductCategory.test.ts +59 -0
  419. package/src/modules/product-management/query/getProductCategory.ts +18 -0
  420. package/src/modules/product-management/query/getProductCategoryAssignment.generated.ts +5 -0
  421. package/src/modules/product-management/query/getProductCategoryAssignment.test.ts +37 -0
  422. package/src/modules/product-management/query/getProductCategoryAssignment.ts +18 -0
  423. package/src/modules/product-management/query/getProductVariant.generated.ts +5 -0
  424. package/src/modules/product-management/query/getProductVariant.test.ts +43 -0
  425. package/src/modules/product-management/query/getProductVariant.ts +20 -0
  426. package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.generated.ts +5 -0
  427. package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.test.ts +31 -0
  428. package/src/modules/product-management/query/listAttributeAssignmentsByAttribute.ts +16 -0
  429. package/src/modules/product-management/query/listCategoryAssignmentsByProduct.generated.ts +5 -0
  430. package/src/modules/product-management/query/listCategoryAssignmentsByProduct.test.ts +31 -0
  431. package/src/modules/product-management/query/listCategoryAssignmentsByProduct.ts +16 -0
  432. package/src/modules/product-management/query/listProductAttributeAssignments.generated.ts +5 -0
  433. package/src/modules/product-management/query/listProductAttributeAssignments.test.ts +31 -0
  434. package/src/modules/product-management/query/listProductAttributeAssignments.ts +16 -0
  435. package/src/modules/product-management/query/listProductAttributeValues.generated.ts +5 -0
  436. package/src/modules/product-management/query/listProductAttributeValues.test.ts +31 -0
  437. package/src/modules/product-management/query/listProductAttributeValues.ts +17 -0
  438. package/src/modules/product-management/query/listProductCategoryAssignments.generated.ts +5 -0
  439. package/src/modules/product-management/query/listProductCategoryAssignments.test.ts +31 -0
  440. package/src/modules/product-management/query/listProductCategoryAssignments.ts +16 -0
  441. package/src/modules/product-management/query/listProductCategoryChildren.generated.ts +5 -0
  442. package/src/modules/product-management/query/listProductCategoryChildren.test.ts +31 -0
  443. package/src/modules/product-management/query/listProductCategoryChildren.ts +16 -0
  444. package/src/modules/product-management/query/listProductVariants.generated.ts +5 -0
  445. package/src/modules/product-management/query/listProductVariants.test.ts +31 -0
  446. package/src/modules/product-management/query/listProductVariants.ts +16 -0
  447. package/src/modules/product-management/tailor.config.ts +13 -0
  448. package/src/modules/product-management/tailor.d.ts +13 -0
  449. package/src/modules/product-management/testing/fixtures.ts +151 -0
  450. package/src/modules/user-management/README.md +9 -3
  451. package/src/modules/user-management/command/activateUser.generated.ts +1 -1
  452. package/src/modules/user-management/command/activateUser.test.ts +12 -65
  453. package/src/modules/user-management/command/activateUser.ts +5 -20
  454. package/src/modules/user-management/command/assignPermissionToRole.generated.ts +1 -1
  455. package/src/modules/user-management/command/assignPermissionToRole.test.ts +25 -60
  456. package/src/modules/user-management/command/assignPermissionToRole.ts +5 -24
  457. package/src/modules/user-management/command/assignRoleToUser.generated.ts +1 -1
  458. package/src/modules/user-management/command/assignRoleToUser.test.ts +35 -87
  459. package/src/modules/user-management/command/assignRoleToUser.ts +5 -24
  460. package/src/modules/user-management/command/createPermission.generated.ts +1 -1
  461. package/src/modules/user-management/command/createPermission.test.ts +23 -33
  462. package/src/modules/user-management/command/createPermission.ts +4 -5
  463. package/src/modules/user-management/command/createRole.generated.ts +1 -1
  464. package/src/modules/user-management/command/createRole.test.ts +17 -27
  465. package/src/modules/user-management/command/createRole.ts +4 -5
  466. package/src/modules/user-management/command/createUser.generated.ts +1 -1
  467. package/src/modules/user-management/command/createUser.test.ts +31 -118
  468. package/src/modules/user-management/command/createUser.ts +7 -25
  469. package/src/modules/user-management/command/deactivateUser.generated.ts +1 -1
  470. package/src/modules/user-management/command/deactivateUser.test.ts +12 -65
  471. package/src/modules/user-management/command/deactivateUser.ts +6 -21
  472. package/src/modules/user-management/command/reactivateUser.generated.ts +1 -1
  473. package/src/modules/user-management/command/reactivateUser.test.ts +13 -66
  474. package/src/modules/user-management/command/reactivateUser.ts +5 -20
  475. package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +1 -1
  476. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +24 -62
  477. package/src/modules/user-management/command/revokePermissionFromRole.ts +5 -24
  478. package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +1 -1
  479. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +24 -60
  480. package/src/modules/user-management/command/revokeRoleFromUser.ts +5 -24
  481. package/src/modules/user-management/docs/commands/ActivateUser.md +7 -0
  482. package/src/modules/user-management/docs/commands/AssignPermissionToRole.md +7 -0
  483. package/src/modules/user-management/docs/commands/AssignRoleToUser.md +9 -0
  484. package/src/modules/user-management/docs/commands/CreatePermission.md +12 -0
  485. package/src/modules/user-management/docs/commands/CreateRole.md +9 -0
  486. package/src/modules/user-management/docs/commands/CreateUser.md +11 -0
  487. package/src/modules/user-management/docs/commands/DeactivateUser.md +7 -0
  488. package/src/modules/user-management/docs/commands/ReactivateUser.md +7 -0
  489. package/src/modules/user-management/docs/commands/RevokePermissionFromRole.md +7 -0
  490. package/src/modules/user-management/docs/commands/RevokeRoleFromUser.md +7 -0
  491. package/src/modules/user-management/index.ts +0 -30
  492. package/src/modules/user-management/lib/errors.generated.ts +14 -14
  493. package/src/modules/user-management/lib/permissions.generated.ts +1 -2
  494. package/src/modules/user-management/lib/recomputeUserPermissions.ts +4 -3
  495. package/src/modules/user-management/lib/types.ts +1 -1
  496. package/src/modules/user-management/module.ts +2 -7
  497. package/src/modules/user-management/tailor.config.ts +6 -4
  498. package/src/modules/user-management/tailor.d.ts +13 -0
  499. package/src/modules/user-management/testing/fixtures.ts +1 -20
  500. package/src/schemas.ts +1 -1
  501. package/src/{modules/shared → shared}/defineCommand.test.ts +23 -7
  502. package/src/{modules/shared → shared}/defineCommand.ts +19 -10
  503. package/src/{modules/shared/internal.ts → shared/index.ts} +9 -1
  504. package/src/shared/pagination.test.ts +43 -0
  505. package/src/shared/pagination.ts +22 -0
  506. package/src/{modules/shared → shared}/types.ts +13 -0
  507. package/src/{modules/testing → testing}/index.ts +14 -7
  508. package/src/testing.ts +1 -1
  509. package/src/util.ts +8 -0
  510. package/templates/config/license.config.json +4 -0
  511. package/templates/scaffold/app/backend/.env.example +1 -0
  512. package/templates/scaffold/app/backend/__dot__gitignore +4 -0
  513. package/templates/scaffold/app/backend/eslint.config.js +32 -0
  514. package/templates/scaffold/app/backend/package.json +31 -0
  515. package/templates/scaffold/app/backend/seed/data/AuditEvent.jsonl +0 -0
  516. package/templates/scaffold/app/backend/seed/data/Permission.jsonl +0 -0
  517. package/templates/scaffold/app/backend/seed/data/Permission.schema.ts +20 -0
  518. package/templates/scaffold/app/backend/seed/data/Role.jsonl +0 -0
  519. package/templates/scaffold/app/backend/seed/data/Role.schema.ts +20 -0
  520. package/templates/scaffold/app/backend/seed/data/RolePermission.jsonl +0 -0
  521. package/templates/scaffold/app/backend/seed/data/RolePermission.schema.ts +24 -0
  522. package/templates/scaffold/app/backend/seed/data/User.jsonl +1 -0
  523. package/templates/scaffold/app/backend/seed/data/User.schema.ts +20 -0
  524. package/templates/scaffold/app/backend/seed/data/UserRole.jsonl +0 -0
  525. package/templates/scaffold/app/backend/seed/data/UserRole.schema.ts +24 -0
  526. package/templates/scaffold/app/backend/seed/data/_User.jsonl +1 -0
  527. package/templates/scaffold/app/backend/seed/data/_User.schema.ts +30 -0
  528. package/templates/scaffold/app/backend/seed/exec.mjs +659 -0
  529. package/templates/scaffold/app/backend/src/executors/permissionCreated.ts +3 -0
  530. package/templates/scaffold/app/backend/src/executors/permissionDeleted.ts +3 -0
  531. package/templates/scaffold/app/backend/src/generated/kysely-tailordb.ts +83 -0
  532. package/templates/scaffold/app/backend/src/modules.ts +9 -0
  533. package/templates/scaffold/app/backend/src/resolvers/createUser.ts +46 -0
  534. package/templates/scaffold/app/backend/tailor.config.ts +68 -0
  535. package/templates/scaffold/app/backend/tailor.d.ts +15 -0
  536. package/templates/scaffold/app/backend/tsconfig.json +19 -0
  537. package/templates/scaffold/app/docs/actors/.gitkeep +0 -0
  538. package/templates/scaffold/app/docs/business-flow/.gitkeep +0 -0
  539. package/templates/scaffold/app/docs/resolver/.gitkeep +0 -0
  540. package/templates/scaffold/app/docs/screen/.gitkeep +0 -0
  541. package/templates/scaffold/app/frontend/.env.example +2 -0
  542. package/templates/scaffold/app/frontend/__dot__gitignore +3 -0
  543. package/templates/scaffold/app/frontend/components.json +23 -0
  544. package/templates/scaffold/app/frontend/eslint.config.js +48 -0
  545. package/templates/scaffold/app/frontend/index.html +13 -0
  546. package/templates/scaffold/app/frontend/package.json +53 -0
  547. package/templates/scaffold/app/frontend/scripts/generate-graphql.mjs +6 -0
  548. package/templates/scaffold/app/frontend/src/App.tsx +58 -0
  549. package/templates/scaffold/app/frontend/src/components/composed/empty-state.tsx +26 -0
  550. package/templates/scaffold/app/frontend/src/components/composed/error-fallback.tsx +28 -0
  551. package/templates/scaffold/app/frontend/src/components/composed/loading.tsx +13 -0
  552. package/templates/scaffold/app/frontend/src/components/ui/badge.tsx +39 -0
  553. package/templates/scaffold/app/frontend/src/components/ui/button.tsx +60 -0
  554. package/templates/scaffold/app/frontend/src/components/ui/card.tsx +75 -0
  555. package/templates/scaffold/app/frontend/src/components/ui/form.tsx +152 -0
  556. package/templates/scaffold/app/frontend/src/components/ui/input.tsx +21 -0
  557. package/templates/scaffold/app/frontend/src/components/ui/label.tsx +21 -0
  558. package/templates/scaffold/app/frontend/src/components/ui/spinner.tsx +16 -0
  559. package/templates/scaffold/app/frontend/src/components/ui/table.tsx +90 -0
  560. package/templates/scaffold/app/frontend/src/graphql/generated/graphql-env.d.ts +103 -0
  561. package/templates/scaffold/app/frontend/src/graphql/generated/schema.graphql +1235 -0
  562. package/templates/scaffold/app/frontend/src/graphql/index.ts +15 -0
  563. package/templates/scaffold/app/frontend/src/index.css +5 -0
  564. package/templates/scaffold/app/frontend/src/lib/auth-client.ts +17 -0
  565. package/templates/scaffold/app/frontend/src/lib/utils.ts +6 -0
  566. package/templates/scaffold/app/frontend/src/main.tsx +10 -0
  567. package/templates/scaffold/app/frontend/src/pages/page.tsx +20 -0
  568. package/templates/scaffold/app/frontend/src/pages/user-management/page.tsx +19 -0
  569. package/templates/scaffold/app/frontend/src/pages/user-management/profile/page.tsx +97 -0
  570. package/templates/scaffold/app/frontend/src/pages/user-management/user/[id]/components/user-detail.tsx +58 -0
  571. package/templates/scaffold/app/frontend/src/pages/user-management/user/[id]/page.tsx +51 -0
  572. package/templates/scaffold/app/frontend/src/pages/user-management/user/components/users-table.tsx +101 -0
  573. package/templates/scaffold/app/frontend/src/pages/user-management/user/create/components/create-user-form.tsx +99 -0
  574. package/templates/scaffold/app/frontend/src/pages/user-management/user/create/page.tsx +19 -0
  575. package/templates/scaffold/app/frontend/src/pages/user-management/user/page.tsx +61 -0
  576. package/templates/scaffold/app/frontend/src/providers/graphql-provider.tsx +21 -0
  577. package/templates/scaffold/app/frontend/tsconfig.app.json +35 -0
  578. package/templates/scaffold/app/frontend/tsconfig.json +16 -0
  579. package/templates/scaffold/app/frontend/tsconfig.node.json +23 -0
  580. package/templates/scaffold/app/frontend/vite.config.ts +23 -0
  581. package/templates/scaffold/module/command/.gitkeep +0 -0
  582. package/templates/scaffold/module/db/.gitkeep +0 -0
  583. package/templates/scaffold/module/executor/.gitkeep +0 -0
  584. package/templates/scaffold/module/generated/.gitkeep +0 -0
  585. package/templates/scaffold/module/index.ts +2 -0
  586. package/templates/scaffold/module/lib/errors.ts +1 -0
  587. package/templates/scaffold/module/lib/types.ts +4 -0
  588. package/templates/scaffold/module/module.ts +7 -0
  589. package/templates/scaffold/module/permissions.ts +3 -0
  590. package/templates/scaffold/module/query/.gitkeep +0 -0
  591. package/templates/scaffold/module/tailor.config.ts +13 -0
  592. package/templates/scaffold/module/testing/fixtures.ts +1 -0
  593. package/templates/workflows/erp-kit-check.yml +37 -0
  594. package/dist/cli.js +0 -1654
  595. package/skills/erp-kit-app-2-breakdown/SKILL.md +0 -88
  596. package/skills/erp-kit-app-3-doc-review/SKILL.md +0 -112
  597. package/skills/erp-kit-app-4-impl-spec/SKILL.md +0 -116
  598. package/skills/erp-kit-app-5-implementation/SKILL.md +0 -149
  599. package/skills/erp-kit-app-5-implementation/references/backend.md +0 -232
  600. package/skills/erp-kit-module-1-docs/SKILL.md +0 -111
  601. package/skills/erp-kit-module-2-feature-breakdown/SKILL.md +0 -76
  602. package/skills/erp-kit-module-3-doc-review/SKILL.md +0 -294
  603. package/skills/erp-kit-module-4-tdd/SKILL.md +0 -94
  604. package/skills/erp-kit-module-4-tdd/references/exports.md +0 -8
  605. package/skills/erp-kit-module-5-impl-review/SKILL.md +0 -410
  606. package/src/commands/scaffold-templates.ts +0 -65
  607. package/src/commands/scaffold.test.ts +0 -171
  608. package/src/commands/scaffold.ts +0 -140
  609. package/src/modules/shared/index.ts +0 -1
  610. package/src/modules/user-management/command/logAuditEvent.generated.ts +0 -6
  611. package/src/modules/user-management/command/logAuditEvent.test.ts +0 -187
  612. package/src/modules/user-management/command/logAuditEvent.ts +0 -56
  613. package/src/modules/user-management/db/auditEvent.ts +0 -47
  614. package/src/modules/user-management/docs/commands/LogAuditEvent.md +0 -37
  615. package/src/modules/user-management/docs/features/audit-trail.md +0 -80
  616. package/src/modules/user-management/docs/models/AuditEvent.md +0 -36
  617. /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/cross-module-dependency.md +0 -0
  618. /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/db-relations.md +0 -0
  619. /package/skills/{erp-kit-module-4-tdd → erp-kit-module-5-impl}/references/models.md +0 -0
  620. /package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/commands.md +0 -0
  621. /package/skills/{erp-kit-module-5-impl-review → erp-kit-module-6-impl-review}/references/testing.md +0 -0
  622. /package/src/modules/{product-management → audit}/.gitkeep +0 -0
  623. /package/src/{modules/shared → shared}/createContext.test.ts +0 -0
  624. /package/src/{modules/shared → shared}/createContext.ts +0 -0
  625. /package/src/{modules/shared → shared}/definePermissions.test.ts +0 -0
  626. /package/src/{modules/shared → shared}/definePermissions.ts +0 -0
  627. /package/src/{modules/shared → shared}/defineQuery.test.ts +0 -0
  628. /package/src/{modules/shared → shared}/defineQuery.ts +0 -0
  629. /package/src/{modules/shared → shared}/entityTypes.ts +0 -0
  630. /package/src/{modules/shared → shared}/errors.ts +0 -0
  631. /package/src/{modules/shared → shared}/requirePermission.test.ts +0 -0
  632. /package/src/{modules/shared → shared}/requirePermission.ts +0 -0
  633. /package/src/{modules/shared → shared}/result.ts +0 -0
package/dist/cli.mjs ADDED
@@ -0,0 +1,1742 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { arg, defineCommand, runMain } from "politty";
4
+ import { z } from "zod";
5
+ import chalk from "chalk";
6
+ import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
7
+ import path, { basename, dirname, join, relative, resolve } from "node:path";
8
+ import { execFile, execSync } from "node:child_process";
9
+ import fg from "fast-glob";
10
+ import { fromMarkdown } from "mdast-util-from-markdown";
11
+ import { toString } from "mdast-util-to-string";
12
+ import { createServer, request } from "node:http";
13
+ import { createServer as createServer$1 } from "node:net";
14
+ import { readFile, readdir, stat } from "node:fs/promises";
15
+ //#region src/util.ts
16
+ const PACKAGE_ROOT = path.resolve(import.meta.dirname, "..");
17
+ function readErpKitVersion() {
18
+ return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8")).version;
19
+ }
20
+ //#endregion
21
+ //#region src/commands/lib/distribute.ts
22
+ const SKILLS_SRC = path.join(PACKAGE_ROOT, "skills");
23
+ const WORKFLOWS_SRC = path.join(PACKAGE_ROOT, "templates", "workflows");
24
+ const CONFIG_SRC = path.join(PACKAGE_ROOT, "templates", "config");
25
+ function copyDirectoryRecursive(srcDir, destDir) {
26
+ let copied = 0;
27
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
28
+ const srcPath = path.join(srcDir, entry.name);
29
+ const destPath = path.join(destDir, entry.name);
30
+ if (entry.isDirectory()) copied += copyDirectoryRecursive(srcPath, destPath);
31
+ else {
32
+ fs.mkdirSync(destDir, { recursive: true });
33
+ fs.copyFileSync(srcPath, destPath);
34
+ copied++;
35
+ }
36
+ }
37
+ return copied;
38
+ }
39
+ function discoverFrameworkSkills() {
40
+ if (!fs.existsSync(SKILLS_SRC)) return [];
41
+ return fs.readdirSync(SKILLS_SRC, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("erp-kit-")).map((entry) => entry.name);
42
+ }
43
+ function copySkills(cwd) {
44
+ const skillsDest = path.join(cwd, ".agents", "skills");
45
+ let removed = 0;
46
+ if (fs.existsSync(skillsDest)) {
47
+ for (const entry of fs.readdirSync(skillsDest)) if (entry.startsWith("erp-kit-")) {
48
+ fs.rmSync(path.join(skillsDest, entry), {
49
+ recursive: true,
50
+ force: true
51
+ });
52
+ removed++;
53
+ }
54
+ }
55
+ let copied = 0;
56
+ for (const skill of discoverFrameworkSkills()) {
57
+ const srcSkillDir = path.join(SKILLS_SRC, skill);
58
+ if (!fs.existsSync(srcSkillDir)) continue;
59
+ const destDir = path.join(skillsDest, skill);
60
+ copied += copyDirectoryRecursive(srcSkillDir, destDir);
61
+ }
62
+ return {
63
+ copied,
64
+ removed
65
+ };
66
+ }
67
+ function copyWorkflows(cwd) {
68
+ const workflowsDest = path.join(cwd, ".github", "workflows");
69
+ if (!fs.existsSync(WORKFLOWS_SRC)) return {
70
+ copied: 0,
71
+ removed: 0
72
+ };
73
+ const workflowFiles = fs.readdirSync(WORKFLOWS_SRC).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
74
+ let copied = 0;
75
+ for (const file of workflowFiles) {
76
+ fs.mkdirSync(workflowsDest, { recursive: true });
77
+ fs.copyFileSync(path.join(WORKFLOWS_SRC, file), path.join(workflowsDest, file));
78
+ copied++;
79
+ }
80
+ return {
81
+ copied,
82
+ removed: 0
83
+ };
84
+ }
85
+ function copyConfigs(cwd) {
86
+ if (!fs.existsSync(CONFIG_SRC)) return {
87
+ copied: 0,
88
+ removed: 0
89
+ };
90
+ let copied = 0;
91
+ for (const entry of fs.readdirSync(CONFIG_SRC)) {
92
+ const destPath = path.join(cwd, entry);
93
+ fs.copyFileSync(path.join(CONFIG_SRC, entry), destPath);
94
+ copied++;
95
+ }
96
+ return {
97
+ copied,
98
+ removed: 0
99
+ };
100
+ }
101
+ function setupSymlink(cwd) {
102
+ const skillsDest = path.join(cwd, ".agents", "skills");
103
+ const claudeSkills = path.join(cwd, ".claude", "skills");
104
+ const relTarget = path.relative(path.dirname(claudeSkills), skillsDest);
105
+ let exists = false;
106
+ try {
107
+ fs.lstatSync(claudeSkills);
108
+ exists = true;
109
+ } catch {}
110
+ if (exists) {
111
+ if (fs.lstatSync(claudeSkills).isSymbolicLink()) {
112
+ if (fs.readlinkSync(claudeSkills) === relTarget) return "already_linked";
113
+ fs.unlinkSync(claudeSkills);
114
+ fs.symlinkSync(relTarget, claudeSkills);
115
+ return "linked";
116
+ }
117
+ return "skipped_directory";
118
+ }
119
+ fs.mkdirSync(path.dirname(claudeSkills), { recursive: true });
120
+ fs.symlinkSync(relTarget, claudeSkills);
121
+ return "linked";
122
+ }
123
+ function isAlreadyInitialized(cwd) {
124
+ const skillsDest = path.join(cwd, ".agents", "skills");
125
+ if (!fs.existsSync(skillsDest)) return false;
126
+ return fs.readdirSync(skillsDest).some((entry) => entry.startsWith("erp-kit-"));
127
+ }
128
+ //#endregion
129
+ //#region src/commands/init.ts
130
+ function runInit(cwd) {
131
+ console.log(chalk.bold("erp-kit init\n"));
132
+ if (isAlreadyInitialized(cwd)) {
133
+ console.log(chalk.yellow("Already initialized. Use `erp-kit update` to refresh framework resources."));
134
+ return 1;
135
+ }
136
+ const skills = copySkills(cwd);
137
+ console.log(chalk.green(` Copied ${skills.copied} skill files to .agents/skills/`));
138
+ const symlink = setupSymlink(cwd);
139
+ if (symlink === "linked") console.log(chalk.green(" .claude/skills -> .agents/skills/ (linked)"));
140
+ else if (symlink === "skipped_directory") console.log(chalk.yellow(" Skipped .claude/skills (directory exists, not a symlink)"));
141
+ const workflows = copyWorkflows(cwd);
142
+ if (workflows.copied > 0) console.log(chalk.green(` Copied ${workflows.copied} workflow(s) to .github/workflows/`));
143
+ const configs = copyConfigs(cwd);
144
+ if (configs.copied > 0) console.log(chalk.green(` Copied ${configs.copied} config file(s)`));
145
+ console.log(chalk.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
146
+ return 0;
147
+ }
148
+ //#endregion
149
+ //#region src/commands/update.ts
150
+ const VALID_RESOURCES = ["skills", "workflows"];
151
+ function runUpdate(cwd, resources) {
152
+ console.log(chalk.bold("erp-kit update\n"));
153
+ const selected = resources.length === 0 ? new Set(VALID_RESOURCES) : new Set(resources.filter((r) => VALID_RESOURCES.includes(r)));
154
+ if (selected.size === 0) {
155
+ console.error(chalk.red(`Invalid resource. Valid: ${VALID_RESOURCES.join(", ")}`));
156
+ return 1;
157
+ }
158
+ if (selected.has("skills")) {
159
+ const result = copySkills(cwd);
160
+ if (result.removed > 0) console.log(chalk.green(` Removed ${result.removed} stale erp-kit-* skills`));
161
+ console.log(chalk.green(` Copied ${result.copied} skill files to .agents/skills/`));
162
+ }
163
+ if (selected.has("workflows")) {
164
+ const result = copyWorkflows(cwd);
165
+ if (result.copied > 0) console.log(chalk.green(` Copied ${result.copied} workflow(s) to .github/workflows/`));
166
+ }
167
+ console.log(chalk.bold.green("\nDone!"));
168
+ return 0;
169
+ }
170
+ //#endregion
171
+ //#region src/commands/license.ts
172
+ const licenseGroups = {
173
+ reciprocal: [
174
+ "APSL-1.0",
175
+ "APSL-1.1",
176
+ "APSL-1.2",
177
+ "APSL-2.0",
178
+ "CDDL-1.0",
179
+ "CDDL-1.1",
180
+ "CPL-1.0",
181
+ "EPL-1.0",
182
+ "EPL-2.0",
183
+ "FreeImage",
184
+ "IPL-1.0",
185
+ "MPL-1.0",
186
+ "MPL-1.1",
187
+ "MPL-2.0",
188
+ "Ruby"
189
+ ],
190
+ notice: [
191
+ "AFL-1.1",
192
+ "AFL-1.2",
193
+ "AFL-2.0",
194
+ "AFL-2.1",
195
+ "AFL-3.0",
196
+ "Apache-1.0",
197
+ "Apache-1.1",
198
+ "Apache-2.0",
199
+ "Artistic-1.0-cl8",
200
+ "Artistic-1.0-Perl",
201
+ "Artistic-1.0",
202
+ "Artistic-2.0",
203
+ "BSL-1.0",
204
+ "BSD-2-Clause-FreeBSD",
205
+ "BSD-2-Clause-NetBSD",
206
+ "BSD-2-Clause",
207
+ "BSD-3-Clause-Attribution",
208
+ "BSD-3-Clause-Clear",
209
+ "BSD-3-Clause-LBNL",
210
+ "BSD-3-Clause",
211
+ "BSD-4-Clause",
212
+ "BSD-4-Clause-UC",
213
+ "BSD-Protection",
214
+ "CC-BY-1.0",
215
+ "CC-BY-2.0",
216
+ "CC-BY-2.5",
217
+ "CC-BY-3.0",
218
+ "CC-BY-4.0",
219
+ "FTL",
220
+ "ISC",
221
+ "ImageMagick",
222
+ "Libpng",
223
+ "Lil-1.0",
224
+ "Linux-OpenIB",
225
+ "LPL-1.02",
226
+ "LPL-1.0",
227
+ "MS-PL",
228
+ "MIT",
229
+ "NCSA",
230
+ "OpenSSL",
231
+ "PHP-3.01",
232
+ "PHP-3.0",
233
+ "PIL",
234
+ "Python-2.0",
235
+ "Python-2.0-complete",
236
+ "PostgreSQL",
237
+ "SGI-B-1.0",
238
+ "SGI-B-1.1",
239
+ "SGI-B-2.0",
240
+ "Unicode-DFS-2015",
241
+ "Unicode-DFS-2016",
242
+ "Unicode-TOU",
243
+ "UPL-1.0",
244
+ "W3C-19980720",
245
+ "W3C-20150513",
246
+ "W3C",
247
+ "X11",
248
+ "Xnet",
249
+ "Zend-2.0",
250
+ "zlib-acknowledgement",
251
+ "Zlib",
252
+ "ZPL-1.1",
253
+ "ZPL-2.0",
254
+ "ZPL-2.1"
255
+ ],
256
+ unencumbered: [
257
+ "CC0-1.0",
258
+ "Unlicense",
259
+ "0BSD"
260
+ ]
261
+ };
262
+ function buildAllowSet(config) {
263
+ const set = /* @__PURE__ */ new Set();
264
+ for (const group of config.groups) for (const license of licenseGroups[group]) set.add(license);
265
+ if (config.allow) for (const license of config.allow) set.add(license);
266
+ if (config.deny) for (const license of config.deny) set.delete(license);
267
+ return set;
268
+ }
269
+ function isLicenseAllowed(licenseString, allowSet) {
270
+ if (/\s+(?:OR|AND)\s+/i.test(licenseString)) return licenseString.replace(/[()]/g, "").trim().split(/\s+(?:OR|AND)\s+/i).map((l) => l.trim()).filter((l) => l.length > 0).every((l) => allowSet.has(l));
271
+ return allowSet.has(licenseString);
272
+ }
273
+ function runLicenseList() {
274
+ for (const [group, licenses] of Object.entries(licenseGroups)) {
275
+ console.log(`${group} (${licenses.length} licenses):`);
276
+ for (const license of licenses) console.log(` ${license}`);
277
+ console.log();
278
+ }
279
+ return 0;
280
+ }
281
+ function runLicenseCheck(configPath) {
282
+ const raw = fs.readFileSync(configPath, "utf-8");
283
+ const config = JSON.parse(raw);
284
+ const validGroups = Object.keys(licenseGroups);
285
+ for (const group of config.groups) if (!validGroups.includes(group)) {
286
+ console.error(`Unknown license group: "${group}". Valid groups: ${validGroups.join(", ")}`);
287
+ return 2;
288
+ }
289
+ const allowSet = buildAllowSet(config);
290
+ console.log("Checking licenses...\n");
291
+ execSync("pnpm licenses list", { stdio: "inherit" });
292
+ const output = execSync("pnpm licenses list --json");
293
+ const licensesJson = JSON.parse(output.toString());
294
+ const violations = [];
295
+ for (const [license, packages] of Object.entries(licensesJson)) if (!isLicenseAllowed(license, allowSet)) for (const pkg of packages) violations.push({
296
+ package: pkg.name,
297
+ license
298
+ });
299
+ if (violations.length === 0) {
300
+ console.log("All licenses are allowed.");
301
+ return 0;
302
+ }
303
+ console.error("Found dependencies with disallowed licenses:\n");
304
+ for (const violation of violations) {
305
+ console.error(` - ${violation.package}`);
306
+ console.error(` License: ${violation.license}\n`);
307
+ }
308
+ return 1;
309
+ }
310
+ //#endregion
311
+ //#region src/mdschema.ts
312
+ const require = createRequire(import.meta.url);
313
+ function getMdschemaBin() {
314
+ const pkgPath = require.resolve("@jackchuka/mdschema/package.json");
315
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
316
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
317
+ if (!bin) throw new Error("Could not resolve mdschema binary from package.json bin field");
318
+ return path.join(path.dirname(pkgPath), bin);
319
+ }
320
+ function runMdschema(args, cwd) {
321
+ return new Promise((resolve) => {
322
+ execFile(getMdschemaBin(), args, {
323
+ encoding: "utf-8",
324
+ cwd,
325
+ timeout: 3e4
326
+ }, (error, stdout, stderr) => {
327
+ if (error) {
328
+ const execError = error;
329
+ resolve({
330
+ exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
331
+ stdout: stdout ?? "",
332
+ stderr: stderr ?? ""
333
+ });
334
+ } else resolve({
335
+ exitCode: 0,
336
+ stdout: stdout ?? "",
337
+ stderr: stderr ?? ""
338
+ });
339
+ });
340
+ });
341
+ }
342
+ //#endregion
343
+ //#region src/schemas.ts
344
+ const SCHEMAS_ROOT = path.join(PACKAGE_ROOT, "schemas");
345
+ const MODULE_SCHEMAS = {
346
+ module: path.join(SCHEMAS_ROOT, "module", "module.yml"),
347
+ command: path.join(SCHEMAS_ROOT, "module", "command.yml"),
348
+ model: path.join(SCHEMAS_ROOT, "module", "model.yml"),
349
+ feature: path.join(SCHEMAS_ROOT, "module", "feature.yml"),
350
+ query: path.join(SCHEMAS_ROOT, "module", "query.yml")
351
+ };
352
+ const APP_COMPOSE_SCHEMAS = {
353
+ app: path.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
354
+ actors: path.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
355
+ "business-flow": path.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
356
+ story: path.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
357
+ screen: path.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
358
+ resolver: path.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
359
+ };
360
+ const ALL_SCHEMAS = {
361
+ ...MODULE_SCHEMAS,
362
+ ...APP_COMPOSE_SCHEMAS
363
+ };
364
+ //#endregion
365
+ //#region src/commands/check.ts
366
+ function buildCheckTargets(config) {
367
+ const targets = [];
368
+ if (config.modulesRoot) {
369
+ const m = config.modulesRoot;
370
+ targets.push({
371
+ glob: `${m}/[a-zA-Z]*/docs/features/*.md`,
372
+ schemaKey: "feature"
373
+ }, {
374
+ glob: `${m}/[a-zA-Z]*/docs/commands/*.md`,
375
+ schemaKey: "command"
376
+ }, {
377
+ glob: `${m}/[a-zA-Z]*/docs/models/*.md`,
378
+ schemaKey: "model"
379
+ }, {
380
+ glob: `${m}/[a-zA-Z]*/docs/queries/*.md`,
381
+ schemaKey: "query"
382
+ }, {
383
+ glob: `${m}/[a-zA-Z]*/README.md`,
384
+ schemaKey: "module"
385
+ });
386
+ }
387
+ if (config.appRoot) {
388
+ const a = config.appRoot;
389
+ targets.push({
390
+ glob: `${a}/[a-zA-Z]*/README.md`,
391
+ schemaKey: "app"
392
+ }, {
393
+ glob: `${a}/[a-zA-Z]*/docs/actors/*.md`,
394
+ schemaKey: "actors"
395
+ }, {
396
+ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`,
397
+ schemaKey: "business-flow"
398
+ }, {
399
+ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`,
400
+ schemaKey: "story"
401
+ }, {
402
+ glob: `${a}/[a-zA-Z]*/docs/screen/*.md`,
403
+ schemaKey: "screen"
404
+ }, {
405
+ glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`,
406
+ schemaKey: "resolver"
407
+ });
408
+ }
409
+ return targets;
410
+ }
411
+ async function runCheck(config, cwd) {
412
+ const targets = buildCheckTargets(config);
413
+ const results = await Promise.all(targets.map(async (target) => {
414
+ const schemaPath = ALL_SCHEMAS[target.schemaKey];
415
+ if (!schemaPath) {
416
+ console.error(`Unknown schema key: ${target.schemaKey}`);
417
+ return 2;
418
+ }
419
+ const { exitCode, stdout, stderr } = await runMdschema([
420
+ "check",
421
+ target.glob,
422
+ "--schema",
423
+ schemaPath
424
+ ], cwd);
425
+ if (stdout.trim()) console.log(stdout);
426
+ if (stderr.trim()) console.error(stderr);
427
+ return exitCode;
428
+ }));
429
+ if (results.includes(2)) return 2;
430
+ return results.some((code) => code !== 0) ? 1 : 0;
431
+ }
432
+ //#endregion
433
+ //#region src/commands/init-module.ts
434
+ function runInitModule(name, dir) {
435
+ const moduleDir = path.resolve(dir, name);
436
+ if (fs.existsSync(moduleDir)) {
437
+ console.error(`Directory already exists: ${moduleDir}`);
438
+ return 1;
439
+ }
440
+ fs.mkdirSync(moduleDir, { recursive: true });
441
+ return 0;
442
+ }
443
+ async function runInitModuleWithReadme(name, dir, cwd) {
444
+ const initResult = runInitModule(name, dir);
445
+ if (initResult !== 0) return initResult;
446
+ const moduleDir = path.resolve(cwd, dir, name);
447
+ const readmePath = path.join(moduleDir, "README.md");
448
+ const schemaPath = MODULE_SCHEMAS.module;
449
+ const { exitCode, stdout, stderr } = await runMdschema([
450
+ "generate",
451
+ "--schema",
452
+ schemaPath,
453
+ "--output",
454
+ readmePath
455
+ ], cwd);
456
+ if (stdout.trim()) console.log(stdout);
457
+ if (stderr.trim()) console.error(stderr);
458
+ return exitCode;
459
+ }
460
+ function runInitApp(name, dir) {
461
+ const appDir = path.resolve(dir, name);
462
+ if (fs.existsSync(appDir)) {
463
+ console.error(`Directory already exists: ${appDir}`);
464
+ return 1;
465
+ }
466
+ fs.mkdirSync(appDir, { recursive: true });
467
+ return 0;
468
+ }
469
+ async function runInitAppWithReadme(name, dir, cwd) {
470
+ const initResult = runInitApp(name, dir);
471
+ if (initResult !== 0) return initResult;
472
+ const appDir = path.resolve(cwd, dir, name);
473
+ const readmePath = path.join(appDir, "README.md");
474
+ const schemaPath = APP_COMPOSE_SCHEMAS.app;
475
+ const { exitCode, stdout, stderr } = await runMdschema([
476
+ "generate",
477
+ "--schema",
478
+ schemaPath,
479
+ "--output",
480
+ readmePath
481
+ ], cwd);
482
+ if (stdout.trim()) console.log(stdout);
483
+ if (stderr.trim()) console.error(stderr);
484
+ return exitCode;
485
+ }
486
+ //#endregion
487
+ //#region src/commands/parse-doc-test-cases.ts
488
+ function isHeading$1(node) {
489
+ return node.type === "heading";
490
+ }
491
+ function isList$1(node) {
492
+ return node.type === "list";
493
+ }
494
+ /**
495
+ * Parse test case descriptions from the `## Test Cases` section of a Markdown doc.
496
+ * Uses mdast to traverse the AST instead of string matching.
497
+ */
498
+ function parseTestCasesFromDoc(markdown) {
499
+ const tree = fromMarkdown(markdown);
500
+ const cases = [];
501
+ let collecting = false;
502
+ for (const node of tree.children) {
503
+ if (isHeading$1(node)) {
504
+ if (collecting) break;
505
+ if (node.depth === 2 && toString(node) === "Test Cases") {
506
+ collecting = true;
507
+ continue;
508
+ }
509
+ }
510
+ if (collecting && isList$1(node)) for (const item of node.children) {
511
+ const text = toString(item).trim();
512
+ if (text) cases.push(text);
513
+ }
514
+ }
515
+ return cases;
516
+ }
517
+ /**
518
+ * Parse `it("...")` descriptions from a test file using regex.
519
+ * Test files are not Markdown, so regex is appropriate here.
520
+ */
521
+ function parseItDescriptionsFromTest(content) {
522
+ const descriptions = [];
523
+ const regex = /\bit\(\s*["'`]([^"'`]+)["'`]/g;
524
+ let match;
525
+ while ((match = regex.exec(content)) !== null) descriptions.push(match[1]);
526
+ return descriptions;
527
+ }
528
+ //#endregion
529
+ //#region src/commands/sync-check.ts
530
+ function moduleCategories(root) {
531
+ return [
532
+ {
533
+ name: "command",
534
+ sourcePattern: `${root}/*/command/*.ts`,
535
+ docPattern: `${root}/*/docs/commands/*.md`,
536
+ exclusions: [/\.test\.ts$/, /\.generated\.ts$/]
537
+ },
538
+ {
539
+ name: "model",
540
+ sourcePattern: `${root}/*/db/*.ts`,
541
+ docPattern: `${root}/*/docs/models/*.md`,
542
+ exclusions: [/\.test\.ts$/, /^index\.ts$/]
543
+ },
544
+ {
545
+ name: "query",
546
+ sourcePattern: `${root}/*/query/*.ts`,
547
+ docPattern: `${root}/*/docs/queries/*.md`,
548
+ exclusions: [/\.test\.ts$/]
549
+ }
550
+ ];
551
+ }
552
+ function appComposeCategories(root) {
553
+ return [{
554
+ name: "resolver",
555
+ sourcePattern: `${root}/*/backend/src/modules/**/resolvers/*.ts`,
556
+ docPattern: `${root}/*/docs/resolver/*.md`,
557
+ exclusions: [/\.test\.ts$/, /^index\.ts$/]
558
+ }];
559
+ }
560
+ function shouldExclude(fileName, exclusions) {
561
+ return exclusions.some((pattern) => pattern.test(fileName));
562
+ }
563
+ async function runSyncCheck(config, cwd) {
564
+ const errors = [];
565
+ let totalSources = 0;
566
+ let totalDocs = 0;
567
+ const allCategories = [];
568
+ if (config.modulesRoot) allCategories.push(...moduleCategories(config.modulesRoot));
569
+ if (config.appRoot) allCategories.push(...appComposeCategories(config.appRoot));
570
+ for (const category of allCategories) {
571
+ const sources = await fg(category.sourcePattern, { cwd });
572
+ const docs = await fg(category.docPattern, { cwd });
573
+ const sourceBasenames = /* @__PURE__ */ new Map();
574
+ const docBasenames = /* @__PURE__ */ new Map();
575
+ for (const sourcePath of sources) {
576
+ if (shouldExclude(path.basename(sourcePath), category.exclusions)) continue;
577
+ let basename = path.basename(sourcePath, path.extname(sourcePath));
578
+ if (basename.endsWith(".generated")) basename = basename.slice(0, -10);
579
+ sourceBasenames.set(basename.toLowerCase(), sourcePath);
580
+ }
581
+ for (const docPath of docs) {
582
+ const basename = path.basename(docPath, path.extname(docPath));
583
+ docBasenames.set(basename.toLowerCase(), docPath);
584
+ }
585
+ for (const [basename, sourcePath] of sourceBasenames) if (!docBasenames.has(basename)) errors.push({
586
+ type: "missing-doc",
587
+ category: category.name,
588
+ sourcePath,
589
+ expectedBasename: basename
590
+ });
591
+ for (const [basename, docPath] of docBasenames) if (!sourceBasenames.has(basename)) errors.push({
592
+ type: "orphaned-doc",
593
+ category: category.name,
594
+ docPath,
595
+ expectedBasename: basename
596
+ });
597
+ totalSources += sourceBasenames.size;
598
+ totalDocs += docBasenames.size;
599
+ }
600
+ if (config.modulesRoot) {
601
+ const testCaseErrors = await runTestCaseSyncCheck(config.modulesRoot, cwd);
602
+ errors.push(...testCaseErrors);
603
+ }
604
+ return {
605
+ exitCode: errors.length > 0 ? 1 : 0,
606
+ errors,
607
+ summary: {
608
+ categoriesChecked: allCategories.length,
609
+ totalSources,
610
+ totalDocs
611
+ }
612
+ };
613
+ }
614
+ function testCaseCategories(root) {
615
+ return [{
616
+ name: "command-test-case",
617
+ docPattern: `${root}/*/docs/commands/*.md`,
618
+ testDir: "command"
619
+ }, {
620
+ name: "query-test-case",
621
+ docPattern: `${root}/*/docs/queries/*.md`,
622
+ testDir: "query"
623
+ }];
624
+ }
625
+ function toCamelCase(pascalCase) {
626
+ return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
627
+ }
628
+ async function runTestCaseSyncCheck(root, cwd) {
629
+ const errors = [];
630
+ const categories = testCaseCategories(root);
631
+ for (const category of categories) {
632
+ const docPaths = await fg(category.docPattern, { cwd });
633
+ for (const docPath of docPaths) {
634
+ const docFullPath = path.join(cwd, docPath);
635
+ const docTestCases = parseTestCasesFromDoc(fs.readFileSync(docFullPath, "utf-8"));
636
+ if (docTestCases.length === 0) continue;
637
+ const docBasename = path.basename(docPath, ".md");
638
+ const docsIndex = docPath.indexOf("/docs/");
639
+ if (docsIndex === -1) continue;
640
+ const modulePath = docPath.substring(0, docsIndex);
641
+ const testFileName = `${toCamelCase(docBasename)}.test.ts`;
642
+ const testPath = path.join(modulePath, category.testDir, testFileName);
643
+ const testFullPath = path.join(cwd, testPath);
644
+ if (!fs.existsSync(testFullPath)) continue;
645
+ const itDescriptions = parseItDescriptionsFromTest(fs.readFileSync(testFullPath, "utf-8"));
646
+ const docSet = new Set(docTestCases);
647
+ const testSet = new Set(itDescriptions);
648
+ for (const docCase of docSet) if (!testSet.has(docCase)) errors.push({
649
+ type: "missing-test-case",
650
+ category: category.name,
651
+ docPath,
652
+ sourcePath: testPath,
653
+ expectedBasename: docCase
654
+ });
655
+ for (const testCase of testSet) if (!docSet.has(testCase)) errors.push({
656
+ type: "extra-test-case",
657
+ category: category.name,
658
+ docPath,
659
+ sourcePath: testPath,
660
+ expectedBasename: testCase
661
+ });
662
+ }
663
+ }
664
+ return errors;
665
+ }
666
+ function formatSyncCheckReport(result) {
667
+ const lines = [];
668
+ lines.push(chalk.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
669
+ if (result.errors.length > 0) {
670
+ lines.push(chalk.red.bold("Errors:\n"));
671
+ const byCategory = /* @__PURE__ */ new Map();
672
+ for (const error of result.errors) {
673
+ const existing = byCategory.get(error.category) ?? [];
674
+ existing.push(error);
675
+ byCategory.set(error.category, existing);
676
+ }
677
+ for (const [category, categoryErrors] of byCategory) {
678
+ lines.push(chalk.cyan(` Category: ${category}\n`));
679
+ for (const error of categoryErrors) {
680
+ if (error.type === "missing-doc") {
681
+ lines.push(` ${chalk.red(error.sourcePath)}`);
682
+ lines.push(` ${chalk.yellow("Missing documentation for:")} ${error.expectedBasename}`);
683
+ } else if (error.type === "orphaned-doc") {
684
+ lines.push(` ${chalk.red(error.docPath)}`);
685
+ lines.push(` ${chalk.yellow("Orphaned documentation:")} no source file for ${error.expectedBasename}`);
686
+ } else if (error.type === "missing-test-case") {
687
+ lines.push(` ${chalk.red(error.sourcePath)}`);
688
+ lines.push(` ${chalk.yellow("Missing test case:")} "${error.expectedBasename}" is in doc but not in test`);
689
+ } else if (error.type === "extra-test-case") {
690
+ lines.push(` ${chalk.red(error.sourcePath)}`);
691
+ lines.push(` ${chalk.yellow("Extra test case:")} "${error.expectedBasename}" is in test but not in doc`);
692
+ }
693
+ lines.push("");
694
+ }
695
+ }
696
+ } else lines.push(chalk.green("All source files have corresponding documentation.\n"));
697
+ lines.push(chalk.bold("Summary:"));
698
+ lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
699
+ lines.push(` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`);
700
+ if (result.errors.length > 0) {
701
+ lines.push(chalk.red(` Errors: ${result.errors.length}`));
702
+ lines.push("");
703
+ lines.push(chalk.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
704
+ } else {
705
+ lines.push(chalk.green(" Errors: 0"));
706
+ lines.push("");
707
+ lines.push(chalk.green.bold("docs-sync-check passed."));
708
+ }
709
+ return lines.join("\n");
710
+ }
711
+ //#endregion
712
+ //#region src/generator/parse-command-doc.ts
713
+ function parseCommandDoc(fileName, markdown) {
714
+ const commandName = fileName.charAt(0).toLowerCase() + fileName.slice(1);
715
+ const tree = fromMarkdown(markdown);
716
+ return {
717
+ commandName,
718
+ errors: parseErrorScenarios(tree),
719
+ externalDependencies: parseExternalDependencies(tree)
720
+ };
721
+ }
722
+ function errorCodeToClassName(code) {
723
+ return code.toLowerCase().split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("") + "Error";
724
+ }
725
+ function isHeading(node) {
726
+ return node.type === "heading";
727
+ }
728
+ function isList(node) {
729
+ return node.type === "list";
730
+ }
731
+ function getNodesUnderHeading(tree, headingText) {
732
+ const nodes = [];
733
+ let collecting = false;
734
+ for (const node of tree.children) {
735
+ if (isHeading(node)) {
736
+ if (collecting) break;
737
+ if (node.depth === 2 && toString(node) === headingText) {
738
+ collecting = true;
739
+ continue;
740
+ }
741
+ }
742
+ if (collecting) nodes.push(node);
743
+ }
744
+ return nodes;
745
+ }
746
+ const ERROR_PATTERN = /^([A-Z_]+):\s*(.+)$/;
747
+ function parseErrorScenarios(tree) {
748
+ const nodes = getNodesUnderHeading(tree, "Error Scenarios");
749
+ const errors = [];
750
+ for (const node of nodes) {
751
+ if (!isList(node)) continue;
752
+ for (const item of node.children) {
753
+ const text = toString(item);
754
+ const match = ERROR_PATTERN.exec(text);
755
+ if (match) errors.push({
756
+ code: match[1],
757
+ description: match[2].trim()
758
+ });
759
+ }
760
+ }
761
+ return errors;
762
+ }
763
+ const DEPENDENCY_PATTERN = /^([^:]+)::(.+)$/;
764
+ function parseExternalDependencies(tree) {
765
+ const nodes = getNodesUnderHeading(tree, "External Dependencies");
766
+ const deps = [];
767
+ for (const node of nodes) {
768
+ if (!isList(node)) continue;
769
+ for (const item of node.children) {
770
+ const firstChild = item.children[0];
771
+ if (firstChild?.type !== "paragraph") continue;
772
+ for (const inline of firstChild.children) if (inline.type === "link" || inline.type === "linkReference") {
773
+ const linkText = toString(inline);
774
+ const match = DEPENDENCY_PATTERN.exec(linkText);
775
+ if (match) deps.push({
776
+ module: match[1],
777
+ entity: match[2]
778
+ });
779
+ }
780
+ }
781
+ }
782
+ return deps;
783
+ }
784
+ //#endregion
785
+ //#region src/generator/generate-code.ts
786
+ function moduleNameToPrefix(moduleName) {
787
+ return moduleName.toUpperCase().replace(/-/g, "_");
788
+ }
789
+ function generateErrors(moduleName, docs) {
790
+ const seen = /* @__PURE__ */ new Set();
791
+ const lines = [];
792
+ const prefix = moduleNameToPrefix(moduleName);
793
+ for (const doc of docs) for (const error of doc.errors) {
794
+ if (seen.has(error.code)) continue;
795
+ seen.add(error.code);
796
+ const className = errorCodeToClassName(error.code);
797
+ const prefixedCode = `${prefix}_${error.code}`;
798
+ lines.push(`export const ${className} = createDomainError(`);
799
+ lines.push(` "${className}", "${prefixedCode}",`);
800
+ const escapedDesc = error.description.replace(/`/g, "'");
801
+ lines.push(` (identifier: string) => \`${escapedDesc}: \${identifier}\`,`);
802
+ lines.push(`);`);
803
+ lines.push(``);
804
+ }
805
+ if (lines.length === 0) return "";
806
+ return `// @generated — do not edit
807
+ import { createDomainError } from "../../../shared";
808
+
809
+ ${lines.join("\n")}`;
810
+ }
811
+ function generateCommandStub(doc) {
812
+ const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
813
+ return `import { ok, type CommandContext } from "../../../shared";
814
+ import type { Transaction } from "../generated/kysely-tailordb";
815
+
816
+ export interface ${inputType} {
817
+ // TODO: define input fields
818
+ }
819
+
820
+ export async function run(db: Transaction, input: ${inputType}, ctx: CommandContext) {
821
+ // TODO: implement
822
+ return ok({});
823
+ }
824
+ `;
825
+ }
826
+ function generateTestStub(doc) {
827
+ const pascal = doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1);
828
+ return `import { describe, expect, it } from "vitest";
829
+ import { createMockDb } from "../../../testing/index";
830
+ import type { CommandContext } from "../../../shared";
831
+ import type { Transaction } from "../generated/kysely-tailordb";
832
+ import { run, ${pascal}Input } from "./${doc.commandName}";
833
+
834
+ describe("${doc.commandName} - test scenario", () => {
835
+ const ctx: CommandContext = {
836
+ actorId: "test-actor",
837
+ permissions: ["TODO:${doc.commandName}"],
838
+ };
839
+
840
+ it("should be implemented", async () => {
841
+ const { db } = createMockDb<Transaction>();
842
+ const result = await run(db, {} as ${pascal}Input, ctx);
843
+ expect(result.ok).toBe(true);
844
+ });
845
+ });
846
+ `;
847
+ }
848
+ function generateCommandShell(doc) {
849
+ return [
850
+ `// @generated — do not edit`,
851
+ `import { defineCommand } from "../../../shared";`,
852
+ `import { permissions } from "../lib/permissions.generated";`,
853
+ `import { run } from "./${doc.commandName}";`,
854
+ ``,
855
+ `export const ${doc.commandName} = defineCommand(permissions.${doc.commandName}, run);`,
856
+ ``
857
+ ].join("\n");
858
+ }
859
+ function generateQueryShell(doc) {
860
+ return [
861
+ `// @generated — do not edit`,
862
+ `import { defineQuery } from "../../../shared";`,
863
+ `import { run } from "./${doc.commandName}";`,
864
+ ``,
865
+ `export const ${doc.commandName} = defineQuery(run);`,
866
+ ``
867
+ ].join("\n");
868
+ }
869
+ function generateQueryStub(doc) {
870
+ const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
871
+ return `import type { ReadonlyDB } from "../../../shared";
872
+ import type { DB } from "../generated/kysely-tailordb";
873
+
874
+ export interface ${inputType} {
875
+ // TODO: define input fields
876
+ }
877
+
878
+ export async function run(db: ReadonlyDB<DB>, input: ${inputType}) {
879
+ // TODO: implement
880
+ return {};
881
+ }
882
+ `;
883
+ }
884
+ function generateQueryTestStub(doc) {
885
+ const inputType = `${doc.commandName.charAt(0).toUpperCase() + doc.commandName.slice(1)}Input`;
886
+ return `import { describe, expect, it } from "vitest";
887
+ import { createMockDb } from "../../../testing/index";
888
+ import type { DB } from "../generated/kysely-tailordb";
889
+ import { run, type ${inputType} } from "./${doc.commandName}";
890
+
891
+ describe("${doc.commandName}", () => {
892
+ it("should be implemented", async () => {
893
+ const { db } = createMockDb<DB>();
894
+ const result = await run(db, {} as ${inputType});
895
+ expect(result).toBeDefined();
896
+ });
897
+ });
898
+ `;
899
+ }
900
+ function generateDbStub(modelName) {
901
+ return `import { db, unsafeAllowAllGqlPermission, unsafeAllowAllTypePermission } from "@tailor-platform/sdk";
902
+
903
+ export const ${modelName} = db
904
+ .type("${modelName.charAt(0).toUpperCase() + modelName.slice(1)}", {
905
+ // TODO: define fields
906
+ ...db.fields.timestamps(),
907
+ })
908
+ .permission(unsafeAllowAllTypePermission)
909
+ .gqlPermission(unsafeAllowAllGqlPermission);
910
+ `;
911
+ }
912
+ function generatePermissions(moduleName, commandNames) {
913
+ return `// @generated — do not edit
914
+ import { definePermissions } from "../../../shared";
915
+
916
+ export const { permissions, own, all } = definePermissions("${moduleName}", [
917
+ ${[...commandNames].sort().map((name) => ` "${name}",`).join("\n")}
918
+ ] as const);
919
+ `;
920
+ }
921
+ function copyTemplateDir(srcDir, destDir, replacements, placeholderFiles) {
922
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
923
+ const srcPath = path.join(srcDir, entry.name);
924
+ const destName = entry.name === "__dot__gitignore" ? ".gitignore" : entry.name;
925
+ const destPath = path.join(destDir, destName);
926
+ if (entry.isDirectory()) {
927
+ fs.mkdirSync(destPath, { recursive: true });
928
+ copyTemplateDir(srcPath, destPath, replacements, placeholderFiles);
929
+ } else {
930
+ if (fs.existsSync(destPath)) continue;
931
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
932
+ if (placeholderFiles.has(entry.name)) {
933
+ let content = fs.readFileSync(srcPath, "utf-8");
934
+ for (const [from, to] of Object.entries(replacements)) content = content.replaceAll(from, to);
935
+ fs.writeFileSync(destPath, content);
936
+ } else fs.copyFileSync(srcPath, destPath);
937
+ }
938
+ }
939
+ }
940
+ function scaffoldModuleBoilerplate(moduleDir, moduleName) {
941
+ copyTemplateDir(path.join(PACKAGE_ROOT, "templates", "scaffold", "module"), moduleDir, { "template-module": moduleName }, new Set(["permissions.ts", "tailor.config.ts"]));
942
+ }
943
+ function scaffoldAppBoilerplate(appDir, appName) {
944
+ const templateDir = path.join(PACKAGE_ROOT, "templates", "scaffold", "app");
945
+ const erpKitVersion = readErpKitVersion();
946
+ copyTemplateDir(templateDir, appDir, {
947
+ "template-app-frontend": `${appName}-frontend`,
948
+ "template-app-backend": appName,
949
+ "template-app": appName,
950
+ "\"workspace:*\"": `"${erpKitVersion}"`
951
+ }, new Set([
952
+ "package.json",
953
+ "tailor.config.ts",
954
+ "index.html"
955
+ ]));
956
+ }
957
+ function runGenerateAppCode(appPath) {
958
+ const appName = path.basename(appPath);
959
+ scaffoldAppBoilerplate(appPath, appName);
960
+ console.log(`Generated boilerplate for ${appName}`);
961
+ return 0;
962
+ }
963
+ function runGenerateCode(modulePath, moduleName) {
964
+ scaffoldModuleBoilerplate(modulePath, moduleName);
965
+ const docsDir = path.join(modulePath, "docs", "commands");
966
+ const libDir = path.join(modulePath, "lib");
967
+ const commandDir = path.join(modulePath, "command");
968
+ if (!fs.existsSync(docsDir)) {
969
+ console.log(`No docs/commands/ directory found — skipping code generation`);
970
+ console.log(`Generated boilerplate for ${moduleName}`);
971
+ return 0;
972
+ }
973
+ const mdFiles = fs.readdirSync(docsDir).filter((f) => f.endsWith(".md"));
974
+ if (mdFiles.length === 0) {
975
+ console.log(`No command docs found — skipping code generation`);
976
+ console.log(`Generated boilerplate for ${moduleName}`);
977
+ return 0;
978
+ }
979
+ const parsedDocs = [];
980
+ for (const file of mdFiles) {
981
+ const content = fs.readFileSync(path.join(docsDir, file), "utf-8");
982
+ const name = path.basename(file, ".md");
983
+ parsedDocs.push(parseCommandDoc(name, content));
984
+ }
985
+ const queryDocsDir = path.join(modulePath, "docs", "queries");
986
+ const allDocsForErrors = [...parsedDocs];
987
+ if (fs.existsSync(queryDocsDir)) {
988
+ const queryFiles = fs.readdirSync(queryDocsDir).filter((f) => f.endsWith(".md"));
989
+ for (const file of queryFiles) {
990
+ const content = fs.readFileSync(path.join(queryDocsDir, file), "utf-8");
991
+ const name = path.basename(file, ".md");
992
+ allDocsForErrors.push(parseCommandDoc(name, content));
993
+ }
994
+ }
995
+ let generated = 0;
996
+ const errorsContent = generateErrors(moduleName, allDocsForErrors);
997
+ if (errorsContent) {
998
+ fs.mkdirSync(libDir, { recursive: true });
999
+ const errorsFile = path.join(libDir, "errors.generated.ts");
1000
+ fs.writeFileSync(errorsFile, errorsContent);
1001
+ console.log(` wrote ${path.relative(modulePath, errorsFile)}`);
1002
+ generated++;
1003
+ }
1004
+ const permissionsContent = generatePermissions(moduleName, parsedDocs.map((d) => d.commandName));
1005
+ const permissionsFile = path.join(libDir, "permissions.generated.ts");
1006
+ fs.writeFileSync(permissionsFile, permissionsContent);
1007
+ console.log(` wrote ${path.relative(modulePath, permissionsFile)}`);
1008
+ generated++;
1009
+ fs.mkdirSync(commandDir, { recursive: true });
1010
+ for (const doc of parsedDocs) {
1011
+ const implFile = path.join(commandDir, `${doc.commandName}.ts`);
1012
+ const shellContent = generateCommandShell(doc);
1013
+ const shellFile = path.join(commandDir, `${doc.commandName}.generated.ts`);
1014
+ fs.writeFileSync(shellFile, shellContent);
1015
+ console.log(` wrote ${path.relative(modulePath, shellFile)}`);
1016
+ generated++;
1017
+ if (!fs.existsSync(implFile)) {
1018
+ fs.writeFileSync(implFile, generateCommandStub(doc));
1019
+ console.log(` scaffolded ${path.relative(modulePath, implFile)}`);
1020
+ }
1021
+ const testFile = path.join(commandDir, `${doc.commandName}.test.ts`);
1022
+ if (!fs.existsSync(testFile)) {
1023
+ fs.writeFileSync(testFile, generateTestStub(doc));
1024
+ console.log(` scaffolded ${path.relative(modulePath, testFile)}`);
1025
+ }
1026
+ }
1027
+ if (fs.existsSync(queryDocsDir)) {
1028
+ const queryFiles = fs.readdirSync(queryDocsDir).filter((f) => f.endsWith(".md"));
1029
+ if (queryFiles.length > 0) {
1030
+ const queryDir = path.join(modulePath, "query");
1031
+ fs.mkdirSync(queryDir, { recursive: true });
1032
+ for (const file of queryFiles) {
1033
+ const content = fs.readFileSync(path.join(queryDocsDir, file), "utf-8");
1034
+ const doc = parseCommandDoc(path.basename(file, ".md"), content);
1035
+ const shellFile = path.join(queryDir, `${doc.commandName}.generated.ts`);
1036
+ fs.writeFileSync(shellFile, generateQueryShell(doc));
1037
+ console.log(` wrote ${path.relative(modulePath, shellFile)}`);
1038
+ generated++;
1039
+ const implFile = path.join(queryDir, `${doc.commandName}.ts`);
1040
+ if (!fs.existsSync(implFile)) {
1041
+ fs.writeFileSync(implFile, generateQueryStub(doc));
1042
+ console.log(` scaffolded ${path.relative(modulePath, implFile)}`);
1043
+ }
1044
+ const testFile = path.join(queryDir, `${doc.commandName}.test.ts`);
1045
+ if (!fs.existsSync(testFile)) {
1046
+ fs.writeFileSync(testFile, generateQueryTestStub(doc));
1047
+ console.log(` scaffolded ${path.relative(modulePath, testFile)}`);
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ const modelDocsDir = path.join(modulePath, "docs", "models");
1053
+ if (fs.existsSync(modelDocsDir)) {
1054
+ const modelFiles = fs.readdirSync(modelDocsDir).filter((f) => f.endsWith(".md"));
1055
+ if (modelFiles.length > 0) {
1056
+ const dbDir = path.join(modulePath, "db");
1057
+ fs.mkdirSync(dbDir, { recursive: true });
1058
+ for (const file of modelFiles) {
1059
+ const modelName = path.basename(file, ".md");
1060
+ const camelName = modelName.charAt(0).toLowerCase() + modelName.slice(1);
1061
+ const dbFile = path.join(dbDir, `${camelName}.ts`);
1062
+ if (!fs.existsSync(dbFile)) {
1063
+ fs.writeFileSync(dbFile, generateDbStub(camelName));
1064
+ console.log(` scaffolded ${path.relative(modulePath, dbFile)}`);
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ console.log(`Generated ${generated} file(s) for ${moduleName}`);
1070
+ return 0;
1071
+ }
1072
+ //#endregion
1073
+ //#region src/commands/generate-doc.ts
1074
+ const MODULE_DOC_TYPES = [
1075
+ "feature",
1076
+ "command",
1077
+ "model",
1078
+ "query"
1079
+ ];
1080
+ const APP_DOC_TYPES = [
1081
+ "actors",
1082
+ "business-flow",
1083
+ "story",
1084
+ "screen",
1085
+ "resolver"
1086
+ ];
1087
+ [...MODULE_DOC_TYPES, ...APP_DOC_TYPES];
1088
+ const MODULE_DIR_MAP = {
1089
+ feature: "docs/features",
1090
+ command: "docs/commands",
1091
+ model: "docs/models",
1092
+ query: "docs/queries"
1093
+ };
1094
+ const APP_DIR_MAP = {
1095
+ actors: "docs/actors",
1096
+ "business-flow": "docs/business-flow",
1097
+ screen: "docs/screen",
1098
+ resolver: "docs/resolver"
1099
+ };
1100
+ function resolveDocPath(type, name, modulePath) {
1101
+ if (!name) throw new Error(`Name is required for doc type "${type}"`);
1102
+ if (type === "business-flow") return path.join(modulePath, "docs/business-flow", name, "README.md");
1103
+ if (type === "story") {
1104
+ const parts = name.split("/");
1105
+ if (parts.length !== 2) throw new Error(`Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`);
1106
+ return path.join(modulePath, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
1107
+ }
1108
+ if (MODULE_DIR_MAP[type]) return path.join(modulePath, MODULE_DIR_MAP[type], `${name}.md`);
1109
+ if (APP_DIR_MAP[type]) return path.join(modulePath, APP_DIR_MAP[type], `${name}.md`);
1110
+ throw new Error(`Unknown doc type: ${type}`);
1111
+ }
1112
+ async function runGenerateDoc(type, name, modulePath, cwd) {
1113
+ const outputPath = resolveDocPath(type, name, modulePath);
1114
+ const absoluteOutput = path.resolve(cwd, outputPath);
1115
+ if (fs.existsSync(absoluteOutput)) {
1116
+ console.error(`File already exists: ${outputPath}`);
1117
+ return 1;
1118
+ }
1119
+ const schemaPath = ALL_SCHEMAS[type];
1120
+ if (!schemaPath) {
1121
+ console.error(`No schema found for type: ${type}`);
1122
+ return 2;
1123
+ }
1124
+ try {
1125
+ fs.mkdirSync(path.dirname(absoluteOutput), { recursive: true });
1126
+ } catch (err) {
1127
+ console.error(`Failed to create directory: ${err instanceof Error ? err.message : String(err)}`);
1128
+ return 1;
1129
+ }
1130
+ const { exitCode, stdout, stderr } = await runMdschema([
1131
+ "generate",
1132
+ "--schema",
1133
+ schemaPath,
1134
+ "--output",
1135
+ absoluteOutput
1136
+ ], cwd);
1137
+ if (stdout.trim()) console.log(stdout);
1138
+ if (stderr.trim()) console.error(stderr);
1139
+ return exitCode;
1140
+ }
1141
+ //#endregion
1142
+ //#region src/commands/module/generate.ts
1143
+ const cwd$3 = process.cwd();
1144
+ const pathArgs$2 = z.object({ path: arg(z.string(), {
1145
+ alias: "p",
1146
+ description: "Path to the module directory"
1147
+ }) });
1148
+ const generateCommand$1 = defineCommand({
1149
+ name: "generate",
1150
+ description: "Generate docs and code for a module",
1151
+ subCommands: {
1152
+ doc: defineCommand({
1153
+ name: "doc",
1154
+ description: "Create a documentation file from a schema template",
1155
+ args: pathArgs$2.extend({
1156
+ type: arg(z.enum(MODULE_DOC_TYPES), {
1157
+ positional: true,
1158
+ description: `Doc type (${MODULE_DOC_TYPES.join(", ")})`
1159
+ }),
1160
+ name: arg(z.string().optional(), {
1161
+ positional: true,
1162
+ description: "Item name (required for all types)"
1163
+ })
1164
+ }),
1165
+ run: async (args) => {
1166
+ const exitCode = await runGenerateDoc(args.type, args.name, args.path, cwd$3);
1167
+ process.exit(exitCode);
1168
+ }
1169
+ }),
1170
+ code: defineCommand({
1171
+ name: "code",
1172
+ description: "Generate boilerplate, implementation stubs, and generated code from docs",
1173
+ args: pathArgs$2,
1174
+ run: (args) => {
1175
+ const modulePath = path.resolve(cwd$3, args.path);
1176
+ const moduleName = path.basename(modulePath);
1177
+ console.log(`Generating code for ${moduleName}...`);
1178
+ const exitCode = runGenerateCode(modulePath, moduleName);
1179
+ process.exit(exitCode);
1180
+ }
1181
+ })
1182
+ }
1183
+ });
1184
+ //#endregion
1185
+ //#region src/commands/module/list.ts
1186
+ const MODULES_DIR = join(PACKAGE_ROOT, "src", "modules");
1187
+ const EXCLUDED_DIRS = new Set(["shared", "testing"]);
1188
+ function countFiles(dir, pattern, exclusions) {
1189
+ if (!existsSync(dir)) return 0;
1190
+ return readdirSync(dir).filter((f) => pattern.test(f) && !exclusions.some((p) => p.test(f))).length;
1191
+ }
1192
+ function listModules() {
1193
+ if (!existsSync(MODULES_DIR)) return [];
1194
+ return readdirSync(MODULES_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && !EXCLUDED_DIRS.has(d.name)).map((d) => {
1195
+ const modDir = join(MODULES_DIR, d.name);
1196
+ return {
1197
+ name: d.name,
1198
+ commands: countFiles(join(modDir, "command"), /\.ts$/, [/\.test\.ts$/]),
1199
+ models: countFiles(join(modDir, "db"), /\.ts$/, [/\.test\.ts$/, /^index\.ts$/]),
1200
+ features: countFiles(join(modDir, "docs", "features"), /\.md$/, [])
1201
+ };
1202
+ }).sort((a, b) => a.name.localeCompare(b.name));
1203
+ }
1204
+ function formatModuleList(modules) {
1205
+ if (modules.length === 0) return "No modules found.";
1206
+ const lines = [];
1207
+ lines.push(chalk.bold("Modules:\n"));
1208
+ const nameWidth = Math.max(...modules.map((m) => m.name.length), 4);
1209
+ for (const mod of modules) {
1210
+ const counts = [
1211
+ `${mod.commands} commands`,
1212
+ `${mod.models} models`,
1213
+ `${mod.features} features`
1214
+ ].join(", ");
1215
+ lines.push(` ${mod.name.padEnd(nameWidth)} ${counts}`);
1216
+ }
1217
+ lines.push("");
1218
+ lines.push(`${modules.length} modules`);
1219
+ return lines.join("\n");
1220
+ }
1221
+ function runModuleList() {
1222
+ const modules = listModules();
1223
+ console.log(formatModuleList(modules));
1224
+ return 0;
1225
+ }
1226
+ //#endregion
1227
+ //#region src/commands/module/index.ts
1228
+ const cwd$2 = process.cwd();
1229
+ const pathArgs$1 = z.object({ path: arg(z.string(), {
1230
+ alias: "p",
1231
+ description: "Path to modules directory"
1232
+ }) });
1233
+ const listCommand = defineCommand({
1234
+ name: "list",
1235
+ description: "List available modules",
1236
+ run: () => {
1237
+ const exitCode = runModuleList();
1238
+ process.exit(exitCode);
1239
+ }
1240
+ });
1241
+ const checkCommand$1 = defineCommand({
1242
+ name: "check",
1243
+ description: "Validate module docs against schemas",
1244
+ args: pathArgs$1,
1245
+ run: async (args) => {
1246
+ const exitCode = await runCheck({ modulesRoot: args.path }, cwd$2);
1247
+ process.exit(exitCode);
1248
+ }
1249
+ });
1250
+ const syncCheckCommand$1 = defineCommand({
1251
+ name: "sync-check",
1252
+ description: "Validate source <-> doc correspondence",
1253
+ args: pathArgs$1,
1254
+ run: async (args) => {
1255
+ const result = await runSyncCheck({ modulesRoot: args.path }, cwd$2);
1256
+ console.log(formatSyncCheckReport(result));
1257
+ process.exit(result.exitCode);
1258
+ }
1259
+ });
1260
+ const initCommand$2 = defineCommand({
1261
+ name: "init",
1262
+ description: "Bootstrap a new module with directory structure and README",
1263
+ args: z.object({
1264
+ name: arg(z.string(), {
1265
+ positional: true,
1266
+ description: "Module name"
1267
+ }),
1268
+ dir: arg(z.string(), {
1269
+ positional: true,
1270
+ description: "Parent directory where the module will be created"
1271
+ })
1272
+ }),
1273
+ run: async (args) => {
1274
+ const exitCode = await runInitModuleWithReadme(args.name, args.dir, cwd$2);
1275
+ process.exit(exitCode);
1276
+ }
1277
+ });
1278
+ const moduleCommand = defineCommand({
1279
+ name: "module",
1280
+ description: "Module management",
1281
+ subCommands: {
1282
+ list: listCommand,
1283
+ check: checkCommand$1,
1284
+ "sync-check": syncCheckCommand$1,
1285
+ init: initCommand$2,
1286
+ generate: generateCommand$1
1287
+ }
1288
+ });
1289
+ //#endregion
1290
+ //#region src/commands/app/index.ts
1291
+ const cwd$1 = process.cwd();
1292
+ const pathArgs = z.object({ path: arg(z.string(), {
1293
+ alias: "p",
1294
+ description: "Path to app-compose directory"
1295
+ }) });
1296
+ const checkCommand = defineCommand({
1297
+ name: "check",
1298
+ description: "Validate app docs against schemas",
1299
+ args: pathArgs,
1300
+ run: async (args) => {
1301
+ const exitCode = await runCheck({ appRoot: args.path }, cwd$1);
1302
+ process.exit(exitCode);
1303
+ }
1304
+ });
1305
+ const syncCheckCommand = defineCommand({
1306
+ name: "sync-check",
1307
+ description: "Validate source <-> doc correspondence",
1308
+ args: pathArgs,
1309
+ run: async (args) => {
1310
+ const result = await runSyncCheck({ appRoot: args.path }, cwd$1);
1311
+ console.log(formatSyncCheckReport(result));
1312
+ process.exit(result.exitCode);
1313
+ }
1314
+ });
1315
+ const initCommand$1 = defineCommand({
1316
+ name: "init",
1317
+ description: "Bootstrap a new app with directory structure and README",
1318
+ args: z.object({
1319
+ name: arg(z.string(), {
1320
+ positional: true,
1321
+ description: "App name"
1322
+ }),
1323
+ dir: arg(z.string(), {
1324
+ positional: true,
1325
+ description: "Parent directory where the app will be created"
1326
+ })
1327
+ }),
1328
+ run: async (args) => {
1329
+ const exitCode = await runInitAppWithReadme(args.name, args.dir, cwd$1);
1330
+ process.exit(exitCode);
1331
+ }
1332
+ });
1333
+ const generateCommand = defineCommand({
1334
+ name: "generate",
1335
+ description: "Generate docs and code for an app",
1336
+ subCommands: {
1337
+ doc: defineCommand({
1338
+ name: "doc",
1339
+ description: "Create a documentation file from a schema template",
1340
+ args: pathArgs.extend({
1341
+ type: arg(z.enum(APP_DOC_TYPES), {
1342
+ positional: true,
1343
+ description: `Doc type (${APP_DOC_TYPES.join(", ")})`
1344
+ }),
1345
+ name: arg(z.string().optional(), {
1346
+ positional: true,
1347
+ description: "Item name (required for most types)"
1348
+ })
1349
+ }),
1350
+ run: async (args) => {
1351
+ const exitCode = await runGenerateDoc(args.type, args.name, args.path, cwd$1);
1352
+ process.exit(exitCode);
1353
+ }
1354
+ }),
1355
+ code: defineCommand({
1356
+ name: "code",
1357
+ description: "Generate boilerplate source files from docs",
1358
+ args: pathArgs,
1359
+ run: (args) => {
1360
+ const exitCode = runGenerateAppCode(path.resolve(cwd$1, args.path));
1361
+ process.exit(exitCode);
1362
+ }
1363
+ })
1364
+ }
1365
+ });
1366
+ const appCommand = defineCommand({
1367
+ name: "app",
1368
+ description: "App-compose management",
1369
+ subCommands: {
1370
+ check: checkCommand,
1371
+ "sync-check": syncCheckCommand,
1372
+ init: initCommand$1,
1373
+ generate: generateCommand
1374
+ }
1375
+ });
1376
+ //#endregion
1377
+ //#region src/mockServer.ts
1378
+ /**
1379
+ * Start a Mockoon mock server from a mock.json file.
1380
+ * Returns a handle with the server URL and a stop function.
1381
+ */
1382
+ async function createMockServer(mockJsonPath, port) {
1383
+ const { MockoonServer, createLoggerInstance, listenServerEvents } = await import("@mockoon/commons-server");
1384
+ const resolvedPath = resolve(mockJsonPath);
1385
+ const environment = JSON.parse(readFileSync(resolvedPath, "utf-8"));
1386
+ if (port !== void 0) environment.port = port;
1387
+ const actualPort = environment.port;
1388
+ const logger = createLoggerInstance(null);
1389
+ const server = new MockoonServer(environment, {
1390
+ environmentDirectory: dirname(resolvedPath),
1391
+ enableAdminApi: false,
1392
+ disableTls: true,
1393
+ enableRandomLatency: false,
1394
+ maxTransactionLogs: 100,
1395
+ envVarsPrefix: "MOCKOON_"
1396
+ });
1397
+ listenServerEvents(server, environment, logger, false);
1398
+ await new Promise((resolve, reject) => {
1399
+ server.on("started", resolve);
1400
+ server.on("error", (errorCode, originalError) => {
1401
+ reject(originalError ?? /* @__PURE__ */ new Error(`Mockoon error: ${errorCode}`));
1402
+ });
1403
+ server.start();
1404
+ });
1405
+ return {
1406
+ url: `http://127.0.0.1:${actualPort}`,
1407
+ port: actualPort,
1408
+ stop: () => new Promise((resolve) => {
1409
+ server.on("stopped", resolve);
1410
+ server.stop();
1411
+ })
1412
+ };
1413
+ }
1414
+ //#endregion
1415
+ //#region src/commands/mock/start.ts
1416
+ function readdirSafe(dir) {
1417
+ try {
1418
+ return readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1419
+ } catch {
1420
+ return [];
1421
+ }
1422
+ }
1423
+ function discoverMocks(mocksDir, filters) {
1424
+ const mocks = [];
1425
+ for (const provider of readdirSafe(mocksDir)) {
1426
+ const providerDir = join(mocksDir, provider);
1427
+ for (const scenario of readdirSafe(providerDir)) {
1428
+ const mockPath = join(providerDir, scenario, "mock.json");
1429
+ if (!existsSync(mockPath)) continue;
1430
+ mocks.push({
1431
+ provider,
1432
+ scenario,
1433
+ mockPath
1434
+ });
1435
+ }
1436
+ }
1437
+ if (filters.length === 0) return mocks;
1438
+ return mocks.filter(({ provider, scenario }) => filters.some((f) => f === provider || f === `${provider}/${scenario}`));
1439
+ }
1440
+ function findFreePort() {
1441
+ return new Promise((resolve, reject) => {
1442
+ const srv = createServer$1();
1443
+ srv.listen(0, "127.0.0.1", () => {
1444
+ const addr = srv.address();
1445
+ const port = typeof addr === "object" && addr ? addr.port : 0;
1446
+ srv.close(() => resolve(port));
1447
+ });
1448
+ srv.on("error", reject);
1449
+ });
1450
+ }
1451
+ async function startMock(mock) {
1452
+ const port = await findFreePort();
1453
+ return {
1454
+ server: await createMockServer(mock.mockPath, port),
1455
+ route: `/${mock.provider}/${mock.scenario}`,
1456
+ provider: mock.provider,
1457
+ scenario: mock.scenario
1458
+ };
1459
+ }
1460
+ function createProxy(routeTable) {
1461
+ return createServer((req, res) => {
1462
+ const match = req.url?.match(/^\/([^/?]+)\/([^/?]+)(\/[^?]*)?(\?.*)?$/);
1463
+ if (!match) {
1464
+ res.writeHead(404, { "Content-Type": "application/json" });
1465
+ res.end(JSON.stringify({ error: "Not found. Use /{provider}/{scenario}/..." }));
1466
+ return;
1467
+ }
1468
+ const [, provider, scenario] = match;
1469
+ const key = `/${provider}/${scenario}`;
1470
+ const target = routeTable.get(key);
1471
+ if (!target) {
1472
+ res.writeHead(404, { "Content-Type": "application/json" });
1473
+ res.end(JSON.stringify({
1474
+ error: `Unknown route: ${key}`,
1475
+ available: [...routeTable.keys()]
1476
+ }));
1477
+ return;
1478
+ }
1479
+ const downstream = (req.url ?? "/").slice(key.length) || "/";
1480
+ const proxyReq = request({
1481
+ hostname: "127.0.0.1",
1482
+ port: target.server.port,
1483
+ path: downstream,
1484
+ method: req.method,
1485
+ headers: {
1486
+ ...req.headers,
1487
+ host: `127.0.0.1:${target.server.port}`
1488
+ }
1489
+ }, (proxyRes) => {
1490
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
1491
+ proxyRes.pipe(res);
1492
+ });
1493
+ proxyReq.on("error", (err) => {
1494
+ if (!res.headersSent) res.writeHead(502, { "Content-Type": "application/json" });
1495
+ res.end(JSON.stringify({
1496
+ error: "Bad gateway",
1497
+ detail: err.message
1498
+ }));
1499
+ });
1500
+ req.pipe(proxyReq);
1501
+ });
1502
+ }
1503
+ async function runMockStart(mocksRoot, filters, port) {
1504
+ const mocksDir = resolve(mocksRoot);
1505
+ const mocks = discoverMocks(mocksDir, filters);
1506
+ if (mocks.length === 0) {
1507
+ console.error("No matching mocks found.");
1508
+ if (filters.length > 0) {
1509
+ console.error(`Filters: ${filters.join(", ")}`);
1510
+ console.error(`Available mocks are under: ${relative(process.cwd(), mocksDir)}/`);
1511
+ }
1512
+ return 1;
1513
+ }
1514
+ console.log(`Starting ${mocks.length} mock(s)...\n`);
1515
+ const running = [];
1516
+ for (const mock of mocks) {
1517
+ const info = await startMock(mock);
1518
+ running.push(info);
1519
+ }
1520
+ const routeTable = /* @__PURE__ */ new Map();
1521
+ for (const r of running) routeTable.set(r.route, r);
1522
+ console.log("Route table:");
1523
+ console.log("─".repeat(50));
1524
+ for (const [route, { server }] of routeTable) console.log(` ${route} \u2192 127.0.0.1:${server.port}`);
1525
+ console.log("─".repeat(50));
1526
+ const proxy = createProxy(routeTable);
1527
+ proxy.listen(port, () => {
1528
+ console.log(`\nReverse proxy listening on http://localhost:${port}`);
1529
+ console.log("Press Ctrl+C to stop all mocks.\n");
1530
+ });
1531
+ function shutdown() {
1532
+ console.log("\nShutting down...");
1533
+ proxy.close();
1534
+ for (const { server } of running) server.stop();
1535
+ process.exit(0);
1536
+ }
1537
+ process.on("SIGINT", shutdown);
1538
+ process.on("SIGTERM", shutdown);
1539
+ return new Promise(() => void 0);
1540
+ }
1541
+ //#endregion
1542
+ //#region src/commands/mock/validate.ts
1543
+ function pass(msg) {
1544
+ console.log(chalk.green(`\u2713 ${msg}`));
1545
+ }
1546
+ function fail(ctx, msg) {
1547
+ console.log(chalk.red(`\u2717 ${msg}`));
1548
+ ctx.failures++;
1549
+ }
1550
+ async function subdirs(dir) {
1551
+ const entries = await readdir(dir);
1552
+ const dirs = [];
1553
+ for (const entry of entries) if ((await stat(join(dir, entry))).isDirectory()) dirs.push(entry);
1554
+ return dirs.sort();
1555
+ }
1556
+ function checkStructure(ctx, entries, label) {
1557
+ if (entries.includes("README.md")) pass(`${label}: has README.md`);
1558
+ else fail(ctx, `${label}: missing README.md`);
1559
+ if (entries.includes("mock.json")) {
1560
+ pass(`${label}: has mock.json`);
1561
+ return true;
1562
+ }
1563
+ fail(ctx, `${label}: missing mock.json`);
1564
+ return false;
1565
+ }
1566
+ async function checkSchema(ctx, data, label) {
1567
+ const result = (await import("@mockoon/commons")).EnvironmentSchemaNoFix.validate(data, { abortEarly: false });
1568
+ if (!result.error) pass(`${label}: valid Mockoon schema`);
1569
+ else for (const detail of result.error.details) fail(ctx, `${label}: schema — ${detail.message}`);
1570
+ }
1571
+ function checkResponseQuality(ctx, data, label) {
1572
+ const routes = data.routes ?? [];
1573
+ for (const route of routes) for (const resp of route.responses ?? []) {
1574
+ const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
1575
+ if ((resp.headers ?? []).some((h) => h.key.toLowerCase() === "content-type")) pass(`${respLabel}: has Content-Type`);
1576
+ else fail(ctx, `${respLabel}: missing Content-Type header`);
1577
+ if (resp.label && resp.label.trim().length > 0) pass(`${respLabel}: has label "${resp.label}"`);
1578
+ else fail(ctx, `${respLabel}: missing or empty label`);
1579
+ }
1580
+ }
1581
+ function checkDatabucketRefs(ctx, data, label) {
1582
+ const bucketIds = new Set((data.data ?? []).map((d) => d.id));
1583
+ for (const route of data.routes ?? []) for (const resp of route.responses ?? []) if (resp.bodyType === "DATABUCKET") {
1584
+ const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
1585
+ if (resp.databucketID && bucketIds.has(resp.databucketID)) pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
1586
+ else fail(ctx, `${respLabel}: databucketID "${resp.databucketID}" not found in data array`);
1587
+ }
1588
+ }
1589
+ async function discoverAllScenarios(mocksDir) {
1590
+ const scenarios = [];
1591
+ const providers = await subdirs(mocksDir);
1592
+ for (const provider of providers) {
1593
+ const providerDir = join(mocksDir, provider);
1594
+ for (const scenario of await subdirs(providerDir)) scenarios.push(`${provider}/${scenario}`);
1595
+ }
1596
+ return scenarios;
1597
+ }
1598
+ function resolveScenarioDir(arg) {
1599
+ const abs = resolve(arg);
1600
+ if (basename(abs) === "mock.json") return dirname(abs);
1601
+ return abs;
1602
+ }
1603
+ async function validateScenario(ctx, scenarioDir, mocksDir) {
1604
+ const label = relative(mocksDir, scenarioDir);
1605
+ console.log(chalk.bold(`\n\u2500\u2500 ${label} \u2500\u2500`));
1606
+ let entries;
1607
+ try {
1608
+ entries = await readdir(scenarioDir);
1609
+ } catch {
1610
+ fail(ctx, `${label}: directory not found`);
1611
+ return;
1612
+ }
1613
+ if (!checkStructure(ctx, entries, label)) return;
1614
+ const mockPath = join(scenarioDir, "mock.json");
1615
+ let data;
1616
+ try {
1617
+ data = JSON.parse(await readFile(mockPath, "utf-8"));
1618
+ } catch (err) {
1619
+ fail(ctx, `${label}: invalid JSON \u2014 ${err instanceof Error ? err.message : String(err)}`);
1620
+ return;
1621
+ }
1622
+ await checkSchema(ctx, data, label);
1623
+ checkResponseQuality(ctx, data, label);
1624
+ checkDatabucketRefs(ctx, data, label);
1625
+ }
1626
+ async function runMockValidate(mocksRoot, paths) {
1627
+ const ctx = { failures: 0 };
1628
+ const mocksDir = resolve(mocksRoot);
1629
+ const targets = paths.length > 0 ? paths.map(resolveScenarioDir) : (await discoverAllScenarios(mocksDir)).map((s) => join(mocksDir, s));
1630
+ if (targets.length === 0) {
1631
+ fail(ctx, "No scenarios found under mocks/");
1632
+ return 1;
1633
+ }
1634
+ console.log(chalk.bold("\nValidating mock configs...\n"));
1635
+ for (const target of targets) await validateScenario(ctx, target, mocksDir);
1636
+ console.log(chalk.bold("\n── summary ──"));
1637
+ if (ctx.failures === 0) console.log(chalk.green("✓ All checks passed"));
1638
+ else console.log(chalk.red(`\u2717 ${ctx.failures} check(s) failed`));
1639
+ return ctx.failures === 0 ? 0 : 1;
1640
+ }
1641
+ const mockCommand = defineCommand({
1642
+ name: "mock",
1643
+ description: "Mock API server management",
1644
+ subCommands: {
1645
+ start: defineCommand({
1646
+ name: "start",
1647
+ description: "Start mock API servers with reverse proxy",
1648
+ args: z.object({
1649
+ mocksRoot: arg(z.string().default("./mocks"), { description: "Path to mocks directory" }),
1650
+ port: arg(z.coerce.number().default(3e3), {
1651
+ alias: "p",
1652
+ description: "Reverse proxy port"
1653
+ }),
1654
+ filter: arg(z.array(z.string()).default([]), {
1655
+ positional: true,
1656
+ description: "Filter by provider or provider/scenario"
1657
+ })
1658
+ }),
1659
+ run: async (args) => {
1660
+ const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
1661
+ process.exit(exitCode);
1662
+ }
1663
+ }),
1664
+ validate: defineCommand({
1665
+ name: "validate",
1666
+ description: "Validate mock scenario configs",
1667
+ args: z.object({
1668
+ mocksRoot: arg(z.string().default("./mocks"), { description: "Path to mocks directory" }),
1669
+ paths: arg(z.array(z.string()).default([]), {
1670
+ positional: true,
1671
+ description: "Specific scenario paths to validate"
1672
+ })
1673
+ }),
1674
+ run: async (args) => {
1675
+ const exitCode = await runMockValidate(args.mocksRoot, args.paths);
1676
+ process.exit(exitCode);
1677
+ }
1678
+ })
1679
+ }
1680
+ });
1681
+ //#endregion
1682
+ //#region src/commands/index.ts
1683
+ const cwd = process.cwd();
1684
+ //#endregion
1685
+ //#region src/cli.ts
1686
+ runMain(defineCommand({
1687
+ name: "erp-kit",
1688
+ description: "ERP module framework CLI",
1689
+ subCommands: {
1690
+ module: moduleCommand,
1691
+ app: appCommand,
1692
+ mock: mockCommand,
1693
+ init: defineCommand({
1694
+ name: "init",
1695
+ description: "First-time setup for a consumer repo",
1696
+ run: () => {
1697
+ const exitCode = runInit(cwd);
1698
+ process.exit(exitCode);
1699
+ }
1700
+ }),
1701
+ update: defineCommand({
1702
+ name: "update",
1703
+ description: "Refresh framework-managed resources (skills, workflows)",
1704
+ args: z.object({ resources: arg(z.array(z.string()).default([]), {
1705
+ positional: true,
1706
+ description: "Resources to update (skills, workflows). Defaults to all."
1707
+ }) }),
1708
+ run: (args) => {
1709
+ const exitCode = runUpdate(cwd, args.resources);
1710
+ process.exit(exitCode);
1711
+ }
1712
+ }),
1713
+ license: defineCommand({
1714
+ name: "license",
1715
+ description: "License management",
1716
+ subCommands: {
1717
+ check: defineCommand({
1718
+ name: "check",
1719
+ description: "Check dependency licenses against allowlist",
1720
+ args: z.object({ config: arg(z.string(), {
1721
+ alias: "c",
1722
+ description: "Path to license config JSON file"
1723
+ }) }),
1724
+ run: (args) => {
1725
+ const exitCode = runLicenseCheck(args.config);
1726
+ process.exit(exitCode);
1727
+ }
1728
+ }),
1729
+ list: defineCommand({
1730
+ name: "list",
1731
+ description: "List available license groups",
1732
+ run: () => {
1733
+ const exitCode = runLicenseList();
1734
+ process.exit(exitCode);
1735
+ }
1736
+ })
1737
+ }
1738
+ })
1739
+ }
1740
+ }));
1741
+ //#endregion
1742
+ export {};