@team-codeme/crud-forge 0.0.0-stage → 1.0.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 (415) hide show
  1. package/.cursor/agents/generator-engineer.md +31 -0
  2. package/.cursor/agents/keycloak-engineer.md +33 -0
  3. package/.cursor/agents/qa-engineer.md +32 -0
  4. package/.cursor/agents/security-reviewer.md +30 -0
  5. package/.cursor/mcp.json +13 -0
  6. package/.cursor/rules/crudforge-precautions.mdc +16 -0
  7. package/.cursor/rules/playwright-browser.mdc +30 -0
  8. package/.cursor/rules/readme-todo-sync.mdc +13 -0
  9. package/.env.qa.example +19 -0
  10. package/.github/workflows/qa-live.yml +32 -0
  11. package/.github/workflows/qa.yml +27 -0
  12. package/.vscode/settings.json +32 -0
  13. package/QA/IAM-PLAN.md +25 -0
  14. package/QA/IAM-RESULTS.md +39 -0
  15. package/QA/IAM-TESTCASES.md +36 -0
  16. package/QA/IAM-TODO.md +7 -0
  17. package/QA/KEYCLOAK-BOOTSTRAP.md +105 -0
  18. package/QA/PLAN.md +43 -0
  19. package/QA/RESULTS.md +59 -0
  20. package/QA/TESTCASES.md +65 -0
  21. package/QA/TODO.md +14 -0
  22. package/QA/pmb-dev/README.md +49 -0
  23. package/QA/pmb-dev/config/config.yml +152 -0
  24. package/QA/pmb-dev/gdl/i.gdl +17 -0
  25. package/README.md +851 -2
  26. package/Todo.md +207 -0
  27. package/assets/branding/crudforge-logo.png +0 -0
  28. package/assets/branding/crudforge-mark.png +0 -0
  29. package/branding/poster-crudforge.jpg +0 -0
  30. package/chat/last-chat/Investigating Mock Data Configuration.md +428 -0
  31. package/cli.js +167 -0
  32. package/config/default-config.yml +245 -0
  33. package/docs/performance/README.md +217 -0
  34. package/docs/performance/after.json +111 -0
  35. package/docs/performance/before.json +111 -0
  36. package/docs/quality/README.md +276 -0
  37. package/e2e/crudforge.e2e.yml +47 -0
  38. package/e2e/crudforge.field-profile.yml +33 -0
  39. package/e2e/crudforge.iam-live.yml +124 -0
  40. package/e2e/crudforge.iam.yml +62 -0
  41. package/e2e/tests/field-profile.spec.js +152 -0
  42. package/e2e/tests/generated-crud.spec.js +251 -0
  43. package/e2e/tests/iam-live.spec.js +149 -0
  44. package/e2e/tests/iam-pack.spec.js +151 -0
  45. package/e2e/tests/list-state.spec.js +73 -0
  46. package/features.csv +27 -0
  47. package/fuse-starter-pack/branding/crudforge-mark-source.jpg +0 -0
  48. package/fuse-starter-pack/branding/crudforge-mark-source.png +0 -0
  49. package/fuse-starter-pack/branding/crudforge-mark.png +0 -0
  50. package/fuse-starter-pack/branding/crudforge-wordmark-source.jpg +0 -0
  51. package/fuse-starter-pack/branding/crudforge-wordmark-source.png +0 -0
  52. package/fuse-starter-pack/branding/crudforge-wordmark.png +0 -0
  53. package/fuse-starter-pack/fuse-v21.1/Dockerfile +14 -0
  54. package/fuse-starter-pack/fuse-v21.1/LICENSE.md +6 -0
  55. package/fuse-starter-pack/fuse-v21.1/README.md +59 -0
  56. package/fuse-starter-pack/fuse-v21.1/angular.json +113 -0
  57. package/fuse-starter-pack/fuse-v21.1/eslint.config.ts +150 -0
  58. package/fuse-starter-pack/fuse-v21.1/nginx.conf +23 -0
  59. package/fuse-starter-pack/fuse-v21.1/nginx.dev.conf +18 -0
  60. package/fuse-starter-pack/fuse-v21.1/package-lock.json +11513 -0
  61. package/fuse-starter-pack/fuse-v21.1/package.json +69 -0
  62. package/fuse-starter-pack/fuse-v21.1/postcss.config.json +5 -0
  63. package/fuse-starter-pack/fuse-v21.1/prettier.config.ts +15 -0
  64. package/fuse-starter-pack/fuse-v21.1/proxy.conf.json +8 -0
  65. package/fuse-starter-pack/fuse-v21.1/proxy.dev.conf.json +8 -0
  66. package/fuse-starter-pack/fuse-v21.1/public/favicon.png +0 -0
  67. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/Geist-Italic.woff2 +0 -0
  68. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/Geist.woff2 +0 -0
  69. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/GeistMono-Italic.woff2 +0 -0
  70. package/fuse-starter-pack/fuse-v21.1/public/fonts/geist/GeistMono.woff2 +0 -0
  71. package/fuse-starter-pack/fuse-v21.1/public/i18n/en.json +8 -0
  72. package/fuse-starter-pack/fuse-v21.1/public/i18n/es.json +8 -0
  73. package/fuse-starter-pack/fuse-v21.1/public/images/flags/ES.svg +79 -0
  74. package/fuse-starter-pack/fuse-v21.1/public/images/flags/US.svg +14 -0
  75. package/fuse-starter-pack/fuse-v21.1/public/images/flags/where-to-find-other-flags.txt +5 -0
  76. package/fuse-starter-pack/fuse-v21.1/public/images/logo/logo-mark.png +0 -0
  77. package/fuse-starter-pack/fuse-v21.1/public/images/logo/logo.png +0 -0
  78. package/fuse-starter-pack/fuse-v21.1/push.sh +27 -0
  79. package/fuse-starter-pack/fuse-v21.1/src/app/app.config.server.ts +10 -0
  80. package/fuse-starter-pack/fuse-v21.1/src/app/app.config.ts +64 -0
  81. package/fuse-starter-pack/fuse-v21.1/src/app/app.routes.server.ts +8 -0
  82. package/fuse-starter-pack/fuse-v21.1/src/app/app.routes.ts +39 -0
  83. package/fuse-starter-pack/fuse-v21.1/src/app/app.ts +22 -0
  84. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/index.ts +1 -0
  85. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/lucide.ts +1 -0
  86. package/fuse-starter-pack/fuse-v21.1/src/app/core/icons/provider.ts +23 -0
  87. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/index.ts +2 -0
  88. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/local-storage.ts +109 -0
  89. package/fuse-starter-pack/fuse-v21.1/src/app/core/local-storage/provider.ts +15 -0
  90. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/index.ts +2 -0
  91. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/media.ts +64 -0
  92. package/fuse-starter-pack/fuse-v21.1/src/app/core/media/provider.ts +15 -0
  93. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/index.ts +3 -0
  94. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/models/theming.ts +13 -0
  95. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/palette.ts +209 -0
  96. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/provider.ts +24 -0
  97. package/fuse-starter-pack/fuse-v21.1/src/app/core/theming/theming.ts +129 -0
  98. package/fuse-starter-pack/fuse-v21.1/src/app/core/transloco/transloco-http-loader.ts +12 -0
  99. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/data/navigation.ts +176 -0
  100. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/layout.ts +77 -0
  101. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/language-switcher.ts +91 -0
  102. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/navigation.ts +238 -0
  103. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/notifications.ts +252 -0
  104. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/scheme-switcher.ts +58 -0
  105. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/sidebar.ts +31 -0
  106. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/layout/ui/user.ts +132 -0
  107. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/data/academy.ts +753 -0
  108. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/data/model.ts +27 -0
  109. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/features/course.ts +318 -0
  110. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/features/courses.ts +228 -0
  111. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/academy/routes.ts +14 -0
  112. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/data/contacts.ts +4372 -0
  113. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/data/model.ts +34 -0
  114. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/features/contact.ts +239 -0
  115. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/features/contacts.ts +185 -0
  116. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/contacts/routes.ts +16 -0
  117. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/data/file-manager.ts +261 -0
  118. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/data/model.ts +12 -0
  119. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/features/file-manager.ts +131 -0
  120. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/routes.ts +15 -0
  121. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/file-manager/ui/list.ts +164 -0
  122. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/faq.ts +288 -0
  123. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/guide.ts +134 -0
  124. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/home.ts +219 -0
  125. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/features/support.ts +161 -0
  126. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/help-center/routes.ts +33 -0
  127. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/data/model.ts +18 -0
  128. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/data/notes.ts +324 -0
  129. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/features/note.ts +152 -0
  130. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/features/notes.ts +118 -0
  131. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/notes/routes.ts +16 -0
  132. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/data/model.ts +17 -0
  133. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/data/tasks.ts +562 -0
  134. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/features/task.ts +195 -0
  135. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/features/tasks.ts +271 -0
  136. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/apps/tasks/routes.ts +16 -0
  137. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/analytics.ts +1620 -0
  138. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/finance.ts +1065 -0
  139. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/data/project.ts +376 -0
  140. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/analytics/analytics.ts +1096 -0
  141. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/finance/finance.ts +658 -0
  142. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/project/budget-table.ts +188 -0
  143. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/features/project/project.ts +565 -0
  144. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/dashboards/routes.ts +23 -0
  145. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/changelog/changelog.ts +932 -0
  146. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/building.ts +33 -0
  147. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/development.ts +42 -0
  148. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/features/getting-started/installation.ts +47 -0
  149. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/layout.ts +9 -0
  150. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/documentation/routes.ts +45 -0
  151. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/error/features/error-page.ts +70 -0
  152. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/error/routes.ts +21 -0
  153. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/notifications/features/notifications.ts +262 -0
  154. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/notifications/routes.ts +10 -0
  155. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/account.ts +238 -0
  156. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/notifications.ts +134 -0
  157. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/plan-and-billing.ts +238 -0
  158. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/security.ts +211 -0
  159. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/features/team.ts +188 -0
  160. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/layout.ts +109 -0
  161. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/settings/routes.ts +37 -0
  162. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/modules/extras/welcome/welcome.component.ts +22 -0
  163. package/fuse-starter-pack/fuse-v21.1/src/app/domains/admin/routes.ts +95 -0
  164. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/forgot-password/forgot-password.html +54 -0
  165. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/forgot-password/forgot-password.ts +51 -0
  166. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/reset-password/reset-password.html +82 -0
  167. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/reset-password/reset-password.ts +69 -0
  168. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-in/sign-in.html +132 -0
  169. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-in/sign-in.ts +54 -0
  170. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-up/sign-up.html +183 -0
  171. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/features/sign-up/sign-up.ts +56 -0
  172. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/layout.ts +9 -0
  173. package/fuse-starter-pack/fuse-v21.1/src/app/domains/auth/routes.ts +34 -0
  174. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/features/coming-soon.ts +156 -0
  175. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/layout.ts +9 -0
  176. package/fuse-starter-pack/fuse-v21.1/src/app/domains/coming-soon/routes.ts +16 -0
  177. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/layout/layout.ts +9 -0
  178. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/modules/home/features/home.ts +23 -0
  179. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/modules/home/routes.ts +10 -0
  180. package/fuse-starter-pack/fuse-v21.1/src/app/domains/website/routes.ts +30 -0
  181. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.mock.ts +5 -0
  182. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.prod.ts +5 -0
  183. package/fuse-starter-pack/fuse-v21.1/src/environments/environment.ts +5 -0
  184. package/fuse-starter-pack/fuse-v21.1/src/index.html +66 -0
  185. package/fuse-starter-pack/fuse-v21.1/src/main.server.ts +11 -0
  186. package/fuse-starter-pack/fuse-v21.1/src/main.ts +5 -0
  187. package/fuse-starter-pack/fuse-v21.1/src/server.ts +70 -0
  188. package/fuse-starter-pack/fuse-v21.1/src/styles/base/base.css +63 -0
  189. package/fuse-starter-pack/fuse-v21.1/src/styles/base/typography.css +54 -0
  190. package/fuse-starter-pack/fuse-v21.1/src/styles/components/a.css +18 -0
  191. package/fuse-starter-pack/fuse-v21.1/src/styles/components/input.css +22 -0
  192. package/fuse-starter-pack/fuse-v21.1/src/styles/components/kbd.css +55 -0
  193. package/fuse-starter-pack/fuse-v21.1/src/styles/components/material.css +1149 -0
  194. package/fuse-starter-pack/fuse-v21.1/src/styles/styles.css +31 -0
  195. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/theme.css +147 -0
  196. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/utilities.css +563 -0
  197. package/fuse-starter-pack/fuse-v21.1/src/styles/tailwind/variants.css +4 -0
  198. package/fuse-starter-pack/fuse-v21.1/src/styles/third-party/lucide-icons.css +12 -0
  199. package/fuse-starter-pack/fuse-v21.1/tsconfig.app.json +16 -0
  200. package/fuse-starter-pack/fuse-v21.1/tsconfig.json +37 -0
  201. package/fuse-starter-pack/fuse-v21.1/tsconfig.spec.json +10 -0
  202. package/generator/README.md +12 -0
  203. package/generator/issues/.gitkeep +0 -0
  204. package/package.json +56 -4
  205. package/playwright.config.js +29 -0
  206. package/playwright.field-profile.config.js +6 -0
  207. package/playwright.iam-live.config.js +31 -0
  208. package/playwright.iam.config.js +29 -0
  209. package/playwright.list-state.config.js +6 -0
  210. package/playwright.performance.config.js +1 -0
  211. package/run-config-tests.sh +5 -0
  212. package/scripts/e2e-paths.js +89 -0
  213. package/scripts/e2e-prepare-field-profile.js +17 -0
  214. package/scripts/e2e-prepare-iam-live.js +135 -0
  215. package/scripts/e2e-prepare-iam.js +65 -0
  216. package/scripts/e2e-prepare.js +72 -0
  217. package/scripts/e2e-serve-iam-live.js +28 -0
  218. package/scripts/e2e-serve-iam.js +28 -0
  219. package/scripts/e2e-serve.js +28 -0
  220. package/scripts/performance/generate.js +17 -0
  221. package/scripts/performance/measure.js +55 -0
  222. package/scripts/postinstall.js +9 -0
  223. package/scripts/prepare-quality-apps.js +34 -0
  224. package/scripts/run-config-tests.js +63 -0
  225. package/src/assets/logo-with-lable.jpeg +0 -0
  226. package/src/assets/logo.jpeg +0 -0
  227. package/src/commands/bootstrap.js +92 -0
  228. package/src/commands/doctor.js +403 -0
  229. package/src/commands/generate.js +301 -0
  230. package/src/commands/init-config.js +310 -0
  231. package/src/commands/keycloak-options.js +182 -0
  232. package/src/commands/validate.js +52 -0
  233. package/src/config/auth-config.js +183 -0
  234. package/src/config/default-values.js +235 -0
  235. package/src/config/env-refs.js +71 -0
  236. package/src/config/field-preferences.js +99 -0
  237. package/src/config/input-sources.js +104 -0
  238. package/src/config/load-config.js +166 -0
  239. package/src/config/proxy-routes.js +44 -0
  240. package/src/generator/access-control-tools.js +334 -0
  241. package/src/generator/ai-docs.js +691 -0
  242. package/src/generator/application-tests.js +252 -0
  243. package/src/generator/auth-pack.js +965 -0
  244. package/src/generator/embed-config.js +431 -0
  245. package/src/generator/entity-layout.js +30 -0
  246. package/src/generator/fuse-build.js +57 -0
  247. package/src/generator/fuse-runtime.js +344 -0
  248. package/src/generator/generated-banner.js +137 -0
  249. package/src/generator/index.js +874 -0
  250. package/src/generator/project-output.js +126 -0
  251. package/src/generator/team-pack.js +220 -0
  252. package/src/index.js +68 -0
  253. package/src/parser/gdl-parser.js +169 -0
  254. package/src/parser/jdl-parser.js +171 -0
  255. package/src/presets/pmb-test.js +139 -0
  256. package/src/presets/static-source.js +73 -0
  257. package/src/presets/store.js +96 -0
  258. package/src/provision/admin-service.js +148 -0
  259. package/src/provision/bootstrap.js +147 -0
  260. package/src/provision/http.js +123 -0
  261. package/src/provision/keycloak-admin-rest.js +335 -0
  262. package/src/provision/state.js +51 -0
  263. package/src/provision/token.js +52 -0
  264. package/src/templates/angular/auth/access-control.service.ts.ejs +179 -0
  265. package/src/templates/angular/auth/access-ctrl.pipe.ts.ejs +20 -0
  266. package/src/templates/angular/auth/auth.guard.ts.ejs +27 -0
  267. package/src/templates/angular/auth/auth.interceptor.ts.ejs +53 -0
  268. package/src/templates/angular/auth/auth.service.ts.ejs +594 -0
  269. package/src/templates/angular/auth/auth.utils.ts.ejs +28 -0
  270. package/src/templates/angular/auth/current-user.ts.ejs +24 -0
  271. package/src/templates/angular/auth/disabled-account-overlay.component.css.ejs +95 -0
  272. package/src/templates/angular/auth/disabled-account-overlay.component.html.ejs +33 -0
  273. package/src/templates/angular/auth/disabled-account-overlay.component.ts.ejs +23 -0
  274. package/src/templates/angular/auth/fuse-account.ts.ejs +234 -0
  275. package/src/templates/angular/auth/fuse-user.ts.ejs +264 -0
  276. package/src/templates/angular/auth/has-permission.directive.ts.ejs +51 -0
  277. package/src/templates/angular/auth/index.ts.ejs +24 -0
  278. package/src/templates/angular/auth/keycloak-admin.service.ts.ejs +670 -0
  279. package/src/templates/angular/auth/landing.guard.ts.ejs +61 -0
  280. package/src/templates/angular/auth/no-auth.guard.ts.ejs +19 -0
  281. package/src/templates/angular/auth/permission-attribute.util.ts.ejs +182 -0
  282. package/src/templates/angular/auth/sign-in.ts.ejs +60 -0
  283. package/src/templates/angular/entity/_entity_.model.ts.ejs +64 -0
  284. package/src/templates/angular/entity/_entity_.routes.ts.ejs +9 -0
  285. package/src/templates/angular/entity/components/forms/form/_entity_-form.component.html.ejs +168 -0
  286. package/src/templates/angular/entity/components/forms/form/_entity_-form.component.ts.ejs +175 -0
  287. package/src/templates/angular/entity/components/forms/form-page/_entity_-form-page.component.html.ejs +10 -0
  288. package/src/templates/angular/entity/components/forms/form-page/_entity_-form-page.component.ts.ejs +86 -0
  289. package/src/templates/angular/entity/components/forms/update/_entity_-form.service.ts.ejs +136 -0
  290. package/src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.html.ejs +68 -0
  291. package/src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.ts.ejs +69 -0
  292. package/src/templates/angular/entity/components/interaction/filter/_entity_-filter.component.html.ejs +111 -0
  293. package/src/templates/angular/entity/components/interaction/filter/_entity_-filter.component.ts.ejs +102 -0
  294. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.css.ejs +33 -0
  295. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.html.ejs +29 -0
  296. package/src/templates/angular/entity/components/presentation/card/_entity_-card.component.ts.ejs +93 -0
  297. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.css.ejs +54 -0
  298. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.html.ejs +58 -0
  299. package/src/templates/angular/entity/components/presentation/detail/_entity_-detail.component.ts.ejs +47 -0
  300. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.html.ejs +419 -0
  301. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.scss.ejs +48 -0
  302. package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs +592 -0
  303. package/src/templates/angular/entity/service/_entity_.service.ts.ejs +153 -0
  304. package/src/templates/angular/entity/settings/_entity_.settings.ts.ejs +49 -0
  305. package/src/templates/angular/enums/_enum_.model.ts.ejs +11 -0
  306. package/src/templates/angular/iam/change-password.component.html.ejs +39 -0
  307. package/src/templates/angular/iam/change-password.component.ts.ejs +44 -0
  308. package/src/templates/angular/iam/no-access.component.ts.ejs +17 -0
  309. package/src/templates/angular/iam/role-list.component.html.ejs +331 -0
  310. package/src/templates/angular/iam/role-list.component.scss.ejs +107 -0
  311. package/src/templates/angular/iam/role-list.component.ts.ejs +597 -0
  312. package/src/templates/angular/iam/role-management/role-header.component.html.ejs +51 -0
  313. package/src/templates/angular/iam/role-management/role-header.component.ts.ejs +45 -0
  314. package/src/templates/angular/iam/role-management/role-inheritance.component.html.ejs +15 -0
  315. package/src/templates/angular/iam/role-management/role-inheritance.component.ts.ejs +19 -0
  316. package/src/templates/angular/iam/role-management/role-permissions-panel.component.css.ejs +24 -0
  317. package/src/templates/angular/iam/role-management/role-permissions-panel.component.html.ejs +108 -0
  318. package/src/templates/angular/iam/role-management/role-permissions-panel.component.ts.ejs +122 -0
  319. package/src/templates/angular/iam/role-management/role-users.component.html.ejs +104 -0
  320. package/src/templates/angular/iam/role-management/role-users.component.ts.ejs +53 -0
  321. package/src/templates/angular/iam/role-management/user-select-dialog.component.html.ejs +113 -0
  322. package/src/templates/angular/iam/role-management/user-select-dialog.component.ts.ejs +157 -0
  323. package/src/templates/angular/iam/role-permission-matrix.component.html.ejs +355 -0
  324. package/src/templates/angular/iam/role-permission-matrix.component.scss.ejs +217 -0
  325. package/src/templates/angular/iam/role-permission-matrix.component.ts.ejs +800 -0
  326. package/src/templates/angular/iam/screen-catalog.ts.ejs +147 -0
  327. package/src/templates/angular/iam/shared/confirm-dialog.component.ts.ejs +83 -0
  328. package/src/templates/angular/iam/shared/matrix-hierarchy.util.ts.ejs +196 -0
  329. package/src/templates/angular/iam/shared/permission-matrix-sound.ts.ejs +59 -0
  330. package/src/templates/angular/iam/shared/role-display.util.ts.ejs +43 -0
  331. package/src/templates/angular/iam/shared/role-permission-skeleton.component.ts.ejs +54 -0
  332. package/src/templates/angular/iam/shared/screen-matrix.util.ts.ejs +554 -0
  333. package/src/templates/angular/iam/shared/screen-navigation-map.ts.ejs +187 -0
  334. package/src/templates/angular/iam/shared/screen-navigation.registry.ts.ejs +64 -0
  335. package/src/templates/angular/iam/shared/text-highlight.pipe.ts.ejs +39 -0
  336. package/src/templates/angular/iam/user-access-matrix.component.html.ejs +212 -0
  337. package/src/templates/angular/iam/user-access-matrix.component.scss.ejs +34 -0
  338. package/src/templates/angular/iam/user-access-matrix.component.ts.ejs +239 -0
  339. package/src/templates/angular/iam/user-detail.component.html.ejs +143 -0
  340. package/src/templates/angular/iam/user-detail.component.ts.ejs +108 -0
  341. package/src/templates/angular/iam/user-form.component.html.ejs +63 -0
  342. package/src/templates/angular/iam/user-form.component.ts.ejs +100 -0
  343. package/src/templates/angular/iam/user-list.component.html.ejs +369 -0
  344. package/src/templates/angular/iam/user-list.component.scss.ejs +242 -0
  345. package/src/templates/angular/iam/user-list.component.ts.ejs +446 -0
  346. package/src/templates/angular/iam/user-management.routes.ts.ejs +47 -0
  347. package/src/templates/angular/iam/user-permission-matrix.component.html.ejs +231 -0
  348. package/src/templates/angular/iam/user-permission-matrix.component.scss.ejs +216 -0
  349. package/src/templates/angular/iam/user-permission-matrix.component.ts.ejs +516 -0
  350. package/src/templates/angular/shared/base/audit-user-name.pipe.ts.ejs +69 -0
  351. package/src/templates/angular/shared/base/base-filter.component.ts.ejs +245 -0
  352. package/src/templates/angular/shared/base/base-form.component.ts.ejs +109 -0
  353. package/src/templates/angular/shared/base/base-list.component.ts.ejs +507 -0
  354. package/src/templates/angular/shared/base/filter-types.ts.ejs +55 -0
  355. package/src/templates/angular/shared/base/input-format.directive.ts.ejs +47 -0
  356. package/src/templates/angular/shared/base/input-format.ts.ejs +15 -0
  357. package/src/templates/angular/shared/confirm-with-input-dialog.component.ts.ejs +91 -0
  358. package/src/templates/angular/shared/confirmation-dialog.component.ts.ejs +80 -0
  359. package/src/templates/angular/shared/list-styles.css.ejs +103 -0
  360. package/src/templates/angular/shared/loading-overlay.component.ts.ejs +24 -0
  361. package/src/templates/angular/shared/resizable.directive.ts.ejs +115 -0
  362. package/src/templates/angular/shared/snackbar-styles.css.ejs +11 -0
  363. package/src/templates/fuse-packs/access-control-tools/access-control-sync.js +819 -0
  364. package/src/templates/fuse-packs/access-control-tools/build-rename-map.js +67 -0
  365. package/src/templates/fuse-packs/access-control-tools/inject-qa-ids.js +14 -0
  366. package/src/templates/fuse-packs/access-control-tools/lib/hierarchy-scanner.js +628 -0
  367. package/src/templates/fuse-packs/access-control-tools/lib/hierarchy-scanner.test.js +323 -0
  368. package/src/templates/fuse-packs/access-control-tools/lib/html-scanner.js +362 -0
  369. package/src/templates/fuse-packs/access-control-tools/lib/id-rules.duplicate.test.js +136 -0
  370. package/src/templates/fuse-packs/access-control-tools/lib/id-rules.js +1095 -0
  371. package/src/templates/fuse-packs/access-control-tools/lib/ts-component-index.js +482 -0
  372. package/src/templates/fuse-packs/access-control-tools/output/.gitkeep +0 -0
  373. package/src/templates/fuse-packs/access-control-tools/run.sh +223 -0
  374. package/src/templates/fuse-packs/access-control-tools/scan-access-control.js +14 -0
  375. package/src/templates/fuse-packs/iam-assets/sound/apple-play.mp3 +0 -0
  376. package/src/templates/fuse-packs/iam-assets/sound/remove.mp3 +0 -0
  377. package/src/templates/generator/README.md +12 -0
  378. package/src/templates/testing/ci.cjs +45 -0
  379. package/src/templates/testing/mock.cjs +149 -0
  380. package/src/templates/testing/playwright.config.cjs +25 -0
  381. package/src/templates/testing/serve.cjs +22 -0
  382. package/src/templates/testing/smoke.spec.cjs +180 -0
  383. package/src/utils/branding.js +39 -0
  384. package/src/utils/colors.js +18 -0
  385. package/src/utils/diff.js +109 -0
  386. package/src/utils/merger.js +115 -0
  387. package/src/validator/backend-diagnostics.js +30 -0
  388. package/src/validator/blueprint-validator.js +276 -0
  389. package/src/validator/diagnostics.js +608 -0
  390. package/src/validator/pattern.js +28 -0
  391. package/test/auth-interceptor.js +84 -0
  392. package/test/auth-provision-checks.js +297 -0
  393. package/test/field-preferences.js +145 -0
  394. package/test/fixtures/field-profile.gdl +7 -0
  395. package/test/fixtures/mock.jdl +17 -0
  396. package/test/fixtures/mock.png +0 -0
  397. package/test/fixtures/performance/config.yml +33 -0
  398. package/test/fixtures/performance/large.gdl +199 -0
  399. package/test/fixtures/performance/small.gdl +7 -0
  400. package/test/fixtures/quality/custom.yml +22 -0
  401. package/test/fixtures/quality/go-duck.yml +22 -0
  402. package/test/fixtures/quality/jhipster.yml +30 -0
  403. package/test/fixtures/quality/representative.gdl +14 -0
  404. package/test/list-state.js +138 -0
  405. package/test/micro-functions-layout.js +95 -0
  406. package/test/performance-generation.js +32 -0
  407. package/test/pmb-preset.js +127 -0
  408. package/test/project-workflow.js +111 -0
  409. package/test/quality-features.js +250 -0
  410. package/test/verify-commands.js +83 -0
  411. package/test/verify.js +959 -0
  412. package/test-files/GDL/example.gdl +15 -0
  413. package/test-files/JDL/example.jdl +15 -0
  414. package/test-files/config/crudforge.config.yml +127 -0
  415. package/test-run.sh +272 -0
@@ -0,0 +1,71 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Read a named environment variable. Never log the value.
6
+ * @param {string} name
7
+ * @returns {string|undefined}
8
+ */
9
+ function resolveEnv(name) {
10
+ if (name == null || name === '') {
11
+ return undefined;
12
+ }
13
+ const value = process.env[String(name)];
14
+ return value;
15
+ }
16
+
17
+ /**
18
+ * Parse KEY=VALUE lines from a dotenv-style file.
19
+ * Existing process.env keys are not overridden. Values are never logged.
20
+ * @param {string} filePath
21
+ */
22
+ function loadEnvFile(filePath) {
23
+ if (filePath == null || String(filePath).trim() === '') {
24
+ throw new Error('loadEnvFile requires a file path.');
25
+ }
26
+
27
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
28
+ if (!fs.existsSync(resolved)) {
29
+ throw new Error(`Env file not found: ${resolved}`);
30
+ }
31
+
32
+ const content = fs.readFileSync(resolved, 'utf8');
33
+ const lines = content.split(/\r?\n/);
34
+
35
+ for (const rawLine of lines) {
36
+ let line = rawLine.trim();
37
+ if (!line || line.startsWith('#')) {
38
+ continue;
39
+ }
40
+ if (line.startsWith('export ')) {
41
+ line = line.slice('export '.length).trim();
42
+ }
43
+
44
+ const eq = line.indexOf('=');
45
+ if (eq <= 0) {
46
+ continue;
47
+ }
48
+
49
+ const key = line.slice(0, eq).trim();
50
+ if (!key) {
51
+ continue;
52
+ }
53
+
54
+ let value = line.slice(eq + 1).trim();
55
+ if (
56
+ (value.startsWith('"') && value.endsWith('"') && value.length >= 2) ||
57
+ (value.startsWith("'") && value.endsWith("'") && value.length >= 2)
58
+ ) {
59
+ value = value.slice(1, -1);
60
+ }
61
+
62
+ if (process.env[key] === undefined) {
63
+ process.env[key] = value;
64
+ }
65
+ }
66
+ }
67
+
68
+ module.exports = {
69
+ resolveEnv,
70
+ loadEnvFile,
71
+ };
@@ -0,0 +1,99 @@
1
+ const TEXT_TYPES = new Set(['String', 'TextBlob']);
2
+ const FORMATS = new Set(['uppercase', 'lowercase', 'none']);
3
+ const AUDIT_NAMES = ['createdBy', 'createdDate', 'lastModifiedBy', 'lastModifiedDate'];
4
+ /** Backend plumbing columns — never rendered in generated UI by default. */
5
+ const TECHNICAL_NAMES = ['debug', 'refType', 'refId'];
6
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+
8
+ function auditFieldNames(config) {
9
+ return config?.fields?.audit?.names || AUDIT_NAMES;
10
+ }
11
+
12
+ function technicalFieldNames(config) {
13
+ if (config?.fields?.technical?.enabled === false) {
14
+ return [];
15
+ }
16
+ return config?.fields?.technical?.names || TECHNICAL_NAMES;
17
+ }
18
+
19
+ function isTechnicalField(fieldName, config) {
20
+ return technicalFieldNames(config).includes(fieldName);
21
+ }
22
+
23
+ function isNonEditableSystemField(fieldName, config) {
24
+ return auditFieldNames(config).includes(fieldName) || isTechnicalField(fieldName, config);
25
+ }
26
+
27
+ /**
28
+ * Whether a field should be omitted from a generated UI surface.
29
+ * Technical fields are always hidden when enabled. Audit fields follow
30
+ * fields.audit.hideIn* / showInDetail (lists keep separate defaultHidden handling).
31
+ */
32
+ function isHiddenFromUi(fieldName, config, surface = 'lists') {
33
+ if (isTechnicalField(fieldName, config)) {
34
+ return true;
35
+ }
36
+ const audit = config?.fields?.audit || {};
37
+ if (!auditFieldNames(config).includes(fieldName)) {
38
+ return false;
39
+ }
40
+ switch (surface) {
41
+ case 'forms':
42
+ return audit.hideInForms !== false;
43
+ case 'filters':
44
+ return audit.hideInFilters !== false;
45
+ case 'search':
46
+ return audit.hideInSearch !== false;
47
+ case 'detail':
48
+ return audit.showInDetail === false;
49
+ case 'cards':
50
+ case 'lists':
51
+ // Cards show audit; lists emit audit as defaultHidden columns.
52
+ // Technical fields are already excluded above.
53
+ return false;
54
+ default:
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function validateFieldPreferences(config, entities, file = 'fields configuration') {
60
+ const fail = (key, message) => { throw new Error(`Invalid configuration "${file}": ${key} ${message}`); };
61
+ const configured = config.fields?.entities;
62
+ if (configured === undefined) return;
63
+ if (!object(configured)) fail('fields.entities', 'must be an object keyed by GDL/JDL entity name.');
64
+ for (const [entityName, entityOptions] of Object.entries(configured)) {
65
+ const prefix = `fields.entities.${entityName}`;
66
+ if (!object(entityOptions) || !object(entityOptions.fields)) fail(prefix, 'must contain a fields object.');
67
+ const entity = entities?.find(item => item.name === entityName);
68
+ if (entities && !entity) fail(prefix, 'references an unknown entity. Use its exact GDL/JDL name.');
69
+ for (const [fieldName, options] of Object.entries(entityOptions.fields)) {
70
+ const key = `${prefix}.fields.${fieldName}.inputFormat`;
71
+ if (!object(options) || !FORMATS.has(options.inputFormat)) fail(key, 'must be uppercase, lowercase, or none.');
72
+ if (entity) {
73
+ const field = entity.fields.find(item => item.fieldName === fieldName);
74
+ if (!field) fail(key, 'references an unknown field. Check the GDL/JDL field name.');
75
+ if (!TEXT_TYPES.has(field.fieldType) || field.fieldTypeIsEnum || fieldName === 'id' || isNonEditableSystemField(fieldName, config)) {
76
+ fail(key, `is incompatible with ${field.fieldType} field ${fieldName}. Configure only editable String or TextBlob fields, excluding IDs, audit, and technical fields.`);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ function entityFieldSettings(config, entity) {
84
+ return Object.fromEntries(entity.fields.filter(field => TEXT_TYPES.has(field.fieldType) && !field.fieldTypeIsEnum && !isNonEditableSystemField(field.fieldName, config)).map(field => [field.fieldName, {
85
+ inputFormat: config.fields?.entities?.[entity.name]?.fields?.[field.fieldName]?.inputFormat ?? (config.formBehavior?.uppercaseInputs ? 'uppercase' : 'none'),
86
+ }]));
87
+ }
88
+
89
+ module.exports = {
90
+ AUDIT_NAMES,
91
+ TECHNICAL_NAMES,
92
+ auditFieldNames,
93
+ technicalFieldNames,
94
+ isTechnicalField,
95
+ isHiddenFromUi,
96
+ isNonEditableSystemField,
97
+ validateFieldPreferences,
98
+ entityFieldSettings,
99
+ };
@@ -0,0 +1,104 @@
1
+ const path = require('path');
2
+ const _ = require('lodash');
3
+
4
+ /**
5
+ * Resolve schema input(s) from config.
6
+ *
7
+ * Supports:
8
+ * input.file — legacy single GDL/JDL
9
+ * input.sources[] — multiple files, each with a service name
10
+ *
11
+ * @returns {{ name: string, file: string, type: string, microservice: string, folder: string }[]}
12
+ */
13
+ function resolveInputSources(config = {}, projectRoot = process.cwd()) {
14
+ const input = config.input || {};
15
+ const defaultType = String(input.type || '').toLowerCase();
16
+ const globalMs = String(config.api?.microservice || '').trim();
17
+ const sources = Array.isArray(input.sources) ? input.sources : [];
18
+
19
+ if (sources.length) {
20
+ return sources.map((raw, index) => {
21
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
22
+ throw new Error(`input.sources[${index}] must be an object with name and file.`);
23
+ }
24
+ const name = String(raw.name || raw.service || '').trim();
25
+ const fileRaw = String(raw.file || raw.path || '').trim();
26
+ if (!name) {
27
+ throw new Error(`input.sources[${index}].name is required (service / module label).`);
28
+ }
29
+ if (!fileRaw) {
30
+ throw new Error(`input.sources[${index}].file is required.`);
31
+ }
32
+ const file = path.isAbsolute(fileRaw) ? fileRaw : path.resolve(projectRoot, fileRaw);
33
+ const ext = path.extname(file).slice(1).toLowerCase();
34
+ const type = String(raw.type || ext || defaultType || 'gdl').toLowerCase();
35
+ if (!['gdl', 'jdl'].includes(type)) {
36
+ throw new Error(`input.sources[${index}].type must be gdl or jdl (got "${type}").`);
37
+ }
38
+ if (ext && ext !== type) {
39
+ throw new Error(`input.sources[${index}]: file extension .${ext} does not match type ${type}.`);
40
+ }
41
+ const microservice = String(raw.microservice || raw.serviceId || globalMs || '').trim();
42
+ const folder = String(raw.folder || _.kebabCase(name)).trim();
43
+ if (!folder || /[\\/]/.test(folder) || ['.', '..'].includes(folder)) {
44
+ throw new Error(`input.sources[${index}].folder must be a single folder name.`);
45
+ }
46
+ return { name, file, type, microservice, folder };
47
+ });
48
+ }
49
+
50
+ const fileRaw = String(input.file || '').trim();
51
+ if (!fileRaw) {
52
+ return [];
53
+ }
54
+ const file = path.isAbsolute(fileRaw) ? fileRaw : path.resolve(projectRoot, fileRaw);
55
+ const ext = path.extname(file).slice(1).toLowerCase();
56
+ const type = String(defaultType || ext || 'jdl').toLowerCase();
57
+ const isGdl = type === 'gdl' || ext === 'gdl';
58
+ const folder =
59
+ String(config.fuse?.folderType || '').trim() || (isGdl ? 'micro-functions' : 'microservice');
60
+ return [
61
+ {
62
+ name: folder === 'micro-functions' ? 'Micro Functions' : folder === 'microservice' ? 'Microservice' : folder,
63
+ file,
64
+ type,
65
+ microservice: globalMs,
66
+ folder,
67
+ },
68
+ ];
69
+ }
70
+
71
+ function validateInputSourcesConfig(config, fail) {
72
+ const sources = config?.input?.sources;
73
+ if (sources === undefined) {
74
+ return;
75
+ }
76
+ if (!Array.isArray(sources)) {
77
+ fail('input.sources must be an array of { name, file, microservice? } objects.');
78
+ }
79
+ const names = new Set();
80
+ const folders = new Set();
81
+ sources.forEach((src, index) => {
82
+ if (!src || typeof src !== 'object' || Array.isArray(src)) {
83
+ fail(`input.sources[${index}] must be an object.`);
84
+ return;
85
+ }
86
+ const name = String(src.name || src.service || '').trim();
87
+ const file = String(src.file || src.path || '').trim();
88
+ if (!name) fail(`input.sources[${index}].name is required.`);
89
+ if (!file) fail(`input.sources[${index}].file is required.`);
90
+ if (names.has(name.toLowerCase())) fail(`input.sources[${index}].name "${name}" is duplicated.`);
91
+ names.add(name.toLowerCase());
92
+ const folder = String(src.folder || _.kebabCase(name)).trim();
93
+ if (folders.has(folder.toLowerCase())) fail(`input.sources[${index}] folder "${folder}" collides with another source.`);
94
+ folders.add(folder.toLowerCase());
95
+ if (src.type != null && !['gdl', 'jdl'].includes(String(src.type).toLowerCase())) {
96
+ fail(`input.sources[${index}].type must be gdl or jdl.`);
97
+ }
98
+ });
99
+ }
100
+
101
+ module.exports = {
102
+ resolveInputSources,
103
+ validateInputSourcesConfig,
104
+ };
@@ -0,0 +1,166 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { parse: parseYaml } = require('yaml');
4
+ const _ = require('lodash');
5
+ const defaultValues = require('./default-values');
6
+ const { deriveAuthUrls } = require('./auth-config');
7
+
8
+ /**
9
+ * Searches for a configuration file in the project.
10
+ * Priority:
11
+ * 1. Custom path if provided
12
+ * 2. config/crudforge.config.yml (generated Fuse apps)
13
+ * 3. crudforge.config.yml / crudforge.config.yaml (repo root)
14
+ * 4. config/config.yml
15
+ * 5. crudforge.config.json
16
+ */
17
+ async function loadConfig(cwd = process.cwd(), customPath = null, options = {}) {
18
+ let rawConfig = {};
19
+ let matchedPath = null;
20
+
21
+ const candidates = customPath
22
+ ? [customPath]
23
+ : [
24
+ 'config/crudforge.config.yml',
25
+ 'config/crudforge.config.yaml',
26
+ 'crudforge.config.yml',
27
+ 'crudforge.config.yaml',
28
+ 'config/config.yml',
29
+ 'crudforge.config.json'
30
+ ];
31
+
32
+ for (const candidate of candidates) {
33
+ const resolved = path.isAbsolute(candidate) ? candidate : path.join(cwd, candidate);
34
+ if (await fs.pathExists(resolved)) {
35
+ matchedPath = resolved;
36
+ try {
37
+ const fileContent = await fs.readFile(resolved, 'utf8');
38
+ if (resolved.endsWith('.json')) {
39
+ rawConfig = JSON.parse(fileContent);
40
+ } else {
41
+ rawConfig = parseYaml(fileContent);
42
+ }
43
+ } catch (err) {
44
+ throw new Error(`Invalid configuration "${resolved}": ${err.message}. Fix this file and retry.`);
45
+ }
46
+ validateConfig(rawConfig, resolved);
47
+ break;
48
+ }
49
+ }
50
+
51
+ if (!matchedPath && (customPath || options.required)) {
52
+ throw new Error(`Configuration not found: ${path.resolve(cwd, customPath || 'config/crudforge.config.yml')}. Run "crudforge init <directory>" or specify --preferences <file>.`);
53
+ }
54
+ const projectRoot = options.projectPaths && matchedPath
55
+ ? (path.basename(path.dirname(matchedPath)) === 'config' ? path.dirname(path.dirname(matchedPath)) : path.dirname(matchedPath))
56
+ : cwd;
57
+
58
+ // Deep merge default configurations with custom ones
59
+ const config = _.mergeWith({}, defaultValues, rawConfig, (_current, incoming) =>
60
+ Array.isArray(incoming) ? incoming.slice() : undefined);
61
+
62
+ if (config.auth) {
63
+ deriveAuthUrls(config.auth);
64
+ }
65
+
66
+ // Normalize absolute paths (single file + multi-source list)
67
+ if (config.input && config.input.file) {
68
+ config.input.file = path.isAbsolute(config.input.file)
69
+ ? config.input.file
70
+ : path.resolve(projectRoot, config.input.file);
71
+ }
72
+ if (Array.isArray(config.input?.sources)) {
73
+ config.input.sources = config.input.sources.map((src) => {
74
+ if (!src || typeof src !== 'object') return src;
75
+ const next = { ...src };
76
+ if (next.file && !path.isAbsolute(next.file)) {
77
+ next.file = path.resolve(projectRoot, next.file);
78
+ }
79
+ if (next.path && !path.isAbsolute(next.path)) {
80
+ next.path = path.resolve(projectRoot, next.path);
81
+ }
82
+ return next;
83
+ });
84
+ }
85
+
86
+ if (config.output && config.output.directory) {
87
+ config.output.directory = path.isAbsolute(config.output.directory)
88
+ ? config.output.directory
89
+ : path.resolve(projectRoot, config.output.directory);
90
+ }
91
+
92
+ if (rawConfig.fuse?.starterPackPath) config.fuse.starterPackPath = path.resolve(projectRoot, rawConfig.fuse.starterPackPath);
93
+
94
+ Object.defineProperty(config, '_configPath', { value: matchedPath, enumerable: false });
95
+ return {
96
+ config,
97
+ configPath: matchedPath,
98
+ projectRoot
99
+ };
100
+ }
101
+
102
+ function validateConfig(config, file) {
103
+ const fail = message => { throw new Error(`Invalid configuration "${file}": ${message}`); };
104
+ if (config?.doctor?.timeoutMs !== undefined && (!Number.isInteger(config.doctor.timeoutMs) || config.doctor.timeoutMs < 100 || config.doctor.timeoutMs > 10000)) fail('doctor.timeoutMs must be 100–10000 milliseconds.');
105
+ if (config?.doctor?.endpoints !== undefined && (!Array.isArray(config.doctor.endpoints) || config.doctor.endpoints.some(e => !e || typeof e.url !== 'string' || typeof e.description !== 'string' || !e.description.trim()))) fail('doctor.endpoints requires url and a description of each documented read-only endpoint.');
106
+ const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
107
+ if (!isObject(config)) fail('expected a YAML/JSON object.');
108
+ function check(values, defaults, prefix = '') {
109
+ for (const [key, value] of Object.entries(values)) {
110
+ const name = prefix + key;
111
+ if (!(key in defaults)) continue;
112
+ const expected = defaults[key];
113
+ if (Array.isArray(expected)) {
114
+ if (!Array.isArray(value)) fail(`${name} must be an array.`);
115
+ } else if (isObject(expected)) {
116
+ if (!isObject(value)) fail(`${name} must be an object.`);
117
+ check(value, expected, name + '.');
118
+ } else if (expected !== null && typeof value !== typeof expected) {
119
+ fail(`${name} must be ${typeof expected}.`);
120
+ }
121
+ }
122
+ }
123
+ check(config, defaultValues);
124
+ require('./input-sources').validateInputSourcesConfig(config, fail);
125
+ require('./field-preferences').validateFieldPreferences(config, undefined, file);
126
+ if (config.proxy?.routes !== undefined) {
127
+ require('./proxy-routes').validateProxyRoutes(config.proxy.routes, `${file}: proxy.routes`);
128
+ }
129
+ if (config.devServer?.port !== undefined && (!Number.isInteger(config.devServer.port) || config.devServer.port < 1 || config.devServer.port > 65535)) {
130
+ fail('devServer.port must be an integer between 1 and 65535.');
131
+ }
132
+ const enums = [
133
+ ['input', 'type', ['gdl', 'jdl']],
134
+ ['output', 'overwritePolicy', ['fail', 'ask', 'skip', 'merge', 'overwrite']],
135
+ ['output', 'structure', ['entity-folder', 'shared-central']],
136
+ ['fuse', 'rendering', ['spa', 'ssr']],
137
+ ['auth', 'provider', ['none', 'keycloak']],
138
+ ['proxy', 'write', ['both', 'dev', 'prod', 'development', 'production']],
139
+ ];
140
+ for (const [section, key, choices] of enums) {
141
+ if (config[section]?.[key] !== undefined && !choices.includes(config[section][key])) {
142
+ fail(`${section}.${key} must be one of: ${choices.join(', ')}.`);
143
+ }
144
+ }
145
+ if (config.output?.directory !== undefined && !config.output.directory.trim()) fail('output.directory must not be empty.');
146
+ if (config.fuse?.starterPackPath !== undefined && typeof config.fuse.starterPackPath !== 'string') fail('fuse.starterPackPath must be a string.');
147
+ if (config.fuse?.includePrebuilt !== undefined && typeof config.fuse.includePrebuilt !== 'boolean') fail('fuse.includePrebuilt must be a boolean.');
148
+ if (config.fuse?.ssrAllowedHosts !== undefined && (!Array.isArray(config.fuse.ssrAllowedHosts) || config.fuse.ssrAllowedHosts.some(host => typeof host !== 'string' || !host.trim()))) fail('fuse.ssrAllowedHosts must be an array of non-empty host strings.');
149
+ if (config.fuse?.folderType && (/[\\/]/.test(config.fuse.folderType) || ['.', '..'].includes(config.fuse.folderType))) {
150
+ fail('fuse.folderType must be a single folder name.');
151
+ }
152
+ for (const [name, service] of Object.entries(config.api?.services || {})) {
153
+ if (typeof service !== 'string' && !isObject(service)) fail(`api.services.${name} must be a URL or service configuration object.`);
154
+ if (isObject(service)) {
155
+ for (const key of ['prod', 'dev', 'target', 'url', 'pathRewrite']) {
156
+ if (service[key] !== undefined && typeof service[key] !== 'string') fail(`api.services.${name}.${key} must be a string.`);
157
+ }
158
+ if (!['prod', 'dev', 'target', 'url'].some(key => service[key]?.trim())) fail(`api.services.${name} needs a prod, dev, target, or url target.`);
159
+ }
160
+ }
161
+ }
162
+
163
+ module.exports = {
164
+ loadConfig,
165
+ validateConfig
166
+ };
@@ -0,0 +1,44 @@
1
+ const ALLOWED = new Set(['target', 'secure', 'changeOrigin', 'ws', 'logLevel', 'pathRewrite']);
2
+
3
+ function validateProxyRoutes(routes, label = 'proxy.routes') {
4
+ if (!routes || typeof routes !== 'object' || Array.isArray(routes)) throw new Error(`${label} must be an object.`);
5
+ for (const [route, entry] of Object.entries(routes)) {
6
+ if (!route.startsWith('/') || /[\s?#]/.test(route)) throw new Error(`${label}: invalid route ${route}.`);
7
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error(`${label}.${route} must be an object.`);
8
+ for (const key of Object.keys(entry)) {
9
+ if (!ALLOWED.has(key)) throw new Error(`${label}.${route}: unsupported option ${key}.`);
10
+ }
11
+ let url;
12
+ try { url = new URL(entry.target); } catch { throw new Error(`${label}.${route}: target must be an absolute HTTP(S) URL.`); }
13
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
14
+ throw new Error(`${label}.${route}: target must be an HTTP(S) URL without credentials, query, or fragment.`);
15
+ }
16
+ for (const key of ['secure', 'changeOrigin', 'ws']) {
17
+ if (entry[key] !== undefined && typeof entry[key] !== 'boolean') throw new Error(`${label}.${route}.${key} must be boolean.`);
18
+ }
19
+ if (entry.logLevel !== undefined && !['debug', 'info', 'warn', 'error', 'silent'].includes(entry.logLevel)) throw new Error(`${label}.${route}: invalid logLevel.`);
20
+ if (entry.pathRewrite !== undefined) {
21
+ if (!entry.pathRewrite || typeof entry.pathRewrite !== 'object' || Array.isArray(entry.pathRewrite)) throw new Error(`${label}.${route}.pathRewrite must be an object.`);
22
+ for (const [pattern, replacement] of Object.entries(entry.pathRewrite)) {
23
+ try { new RegExp(pattern); } catch { throw new Error(`${label}.${route}: invalid rewrite pattern.`); }
24
+ if (typeof replacement !== 'string' || (replacement !== '' && !replacement.startsWith('/')) || /[?#\r\n]/.test(replacement)) {
25
+ throw new Error(`${label}.${route}: rewrite must be an empty string or a URL path.`);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ // Do not import auth headers, cookies, credentials, bypass code, or other proxy hooks.
33
+ function sanitizeProxyRoutes(routes, label) {
34
+ const clean = {};
35
+ if (!routes || typeof routes !== 'object' || Array.isArray(routes)) throw new Error(`${label}: expected a proxy object.`);
36
+ for (const [route, entry] of Object.entries(routes)) {
37
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error(`${label}: invalid entry ${route}.`);
38
+ clean[route] = Object.fromEntries(Object.entries(entry).filter(([key]) => ALLOWED.has(key)));
39
+ }
40
+ validateProxyRoutes(clean, label);
41
+ return clean;
42
+ }
43
+
44
+ module.exports = { validateProxyRoutes, sanitizeProxyRoutes };