@team-codeme/crud-forge 0.0.0-stage → 1.0.1

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,84 @@
1
+ // Behavioral tests of the emitted TypeScript without Angular DI or a live account.
2
+ // Node 24+ is also required by the generated Fuse app.
3
+ const assert = require('assert/strict');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const vm = require('vm');
7
+ const { stripTypeScriptTypes } = require('module');
8
+ const rx = require('rxjs');
9
+
10
+ class HttpErrorResponse extends Error {
11
+ constructor(status, error = {}, url = '/services/example/api/items') {
12
+ super('HTTP error'); this.status = status; this.error = error; this.url = url;
13
+ }
14
+ }
15
+ class Headers {
16
+ constructor(values = {}) { this.values = values; }
17
+ delete(key) { const values = { ...this.values }; delete values[key]; return new Headers(values); }
18
+ set(key, value) { return new Headers({ ...this.values, [key]: value }); }
19
+ }
20
+ function load(name, exported, context) {
21
+ const text = fs.readFileSync(path.join(__dirname, '../src/templates/angular/auth', name), 'utf8')
22
+ .replace(/^import .*?;\n/gm, '').replace(/^@Injectable.*\n/gm, '').replace(/^export /gm, '');
23
+ const module = { exports: {} };
24
+ vm.runInNewContext(stripTypeScriptTypes(text) + `\nmodule.exports = ${exported};`, { module, ...rx, HttpErrorResponse, ...context });
25
+ return module.exports;
26
+ }
27
+
28
+ async function test() {
29
+ if (!stripTypeScriptTypes) throw new Error('Auth behavior tests require Node 24+.');
30
+ const Auth = load('auth.service.ts.ejs', 'AuthService', {});
31
+ const service = Object.create(Auth.prototype);
32
+ const token = '/realms/PMB-DEV/protocol/openid-connect/token';
33
+ assert.equal(service.isTerminalAuthError(new HttpErrorResponse(400, { error: 'invalid_grant' }, token)), true);
34
+ assert.equal(service.isTerminalAuthError(new HttpErrorResponse(401, {}, token)), true);
35
+ for (const error of [new HttpErrorResponse(403, {}, token), new HttpErrorResponse(500, {}, token),
36
+ new HttpErrorResponse(0, {}, token), new HttpErrorResponse(401), new HttpErrorResponse(400, {})]) {
37
+ assert.equal(service.isTerminalAuthError(error), false);
38
+ }
39
+
40
+ async function scenario({ expired = false, refreshError, responses, url = '/services/example/api/items' }) {
41
+ let signOuts = 0, refreshes = 0, calls = 0;
42
+ const sent = [];
43
+ const auth = {
44
+ accessToken: 'old', refreshToken: 'refresh', isAuthenticated: true,
45
+ resetInactivityTimer() {}, tryStartDisabledAccountLockFromAuthError() { return false; },
46
+ isTerminalAuthError: error => service.isTerminalAuthError(error),
47
+ signOut() { signOuts++; },
48
+ refreshAccessToken() {
49
+ refreshes++;
50
+ if (refreshError) return rx.throwError(() => refreshError);
51
+ auth.accessToken = 'fresh'; return rx.of({});
52
+ },
53
+ };
54
+ const interceptor = load('auth.interceptor.ts.ejs', 'authInterceptor', {
55
+ AuthService: {}, inject: () => auth,
56
+ environment: { sso: token, useMockAuth: false }, AuthUtils: { shouldRefreshAccessToken: () => expired },
57
+ });
58
+ const request = { url, headers: new Headers({ Cookie: 'discard', 'X-Tenant-ID': 'discard' }), clone(options) { return { ...this, ...options }; } };
59
+ let error;
60
+ try {
61
+ await rx.firstValueFrom(interceptor(request, req => {
62
+ sent.push(req.headers.values); const response = responses[calls++];
63
+ return response instanceof Error ? rx.throwError(() => response) : rx.of(response);
64
+ }));
65
+ } catch (caught) { error = caught; }
66
+ for (const headers of sent) { assert(!headers.Cookie); assert(!headers['X-Tenant-ID']); }
67
+ return { signOuts, refreshes, calls, sent, error };
68
+ }
69
+ let result = await scenario({ responses: [new HttpErrorResponse(401), { ok: true }] });
70
+ assert.equal(result.refreshes, 1); assert.equal(result.calls, 2); assert.equal(result.signOuts, 0);
71
+ assert.equal(result.sent[1].Authorization, 'Bearer fresh');
72
+ result = await scenario({ responses: [new HttpErrorResponse(403)] });
73
+ assert.equal(result.refreshes, 0); assert.equal(result.signOuts, 0); assert(result.error);
74
+ result = await scenario({ expired: true, responses: [new HttpErrorResponse(401)] });
75
+ assert.equal(result.refreshes, 1); assert.equal(result.calls, 1); assert.equal(result.signOuts, 0); assert(result.error);
76
+ result = await scenario({ expired: true, refreshError: new HttpErrorResponse(500, {}, token), responses: [] });
77
+ assert.equal(result.calls, 0); assert.equal(result.signOuts, 0); assert(result.error);
78
+ result = await scenario({ expired: true, refreshError: new HttpErrorResponse(400, { error: 'invalid_grant' }, token), responses: [] });
79
+ assert.equal(result.signOuts, 1); assert.equal(result.calls, 0);
80
+ result = await scenario({ url: token, responses: [new HttpErrorResponse(401, {}, token)] });
81
+ assert.equal(result.refreshes, 0); assert.equal(result.signOuts, 0); assert(!result.sent[0].Authorization);
82
+ console.log('Auth behavior passed: one refresh/retry, bearer headers, terminal grant rejection, and session preservation on API/transport errors.');
83
+ }
84
+ test().catch(error => { console.error(error); process.exitCode = 1; });
@@ -0,0 +1,297 @@
1
+ const fs = require('fs-extra');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { loadEnvFile, resolveEnv } = require('../src/config/env-refs');
5
+ const { validateAuthConfig, AuthConfigError, deriveAuthUrls, stripSecretsForGenerate } = require('../src/config/auth-config');
6
+ const defaultValues = require('../src/config/default-values');
7
+
8
+ async function runAuthProvisionChecks() {
9
+ console.log('🧪 Testing auth provision config, env refs, and bootstrap exports...');
10
+
11
+ if (defaultValues.auth.provision.enabled !== false) {
12
+ throw new Error('❌ Default auth.provision.enabled must be false so generate never mutates Keycloak.');
13
+ }
14
+ if (defaultValues.auth.provider !== 'none') {
15
+ throw new Error('❌ Default auth.provider must remain none.');
16
+ }
17
+ if (String(defaultValues.auth.serverUrl || '') !== '') {
18
+ throw new Error('❌ Default auth.serverUrl must stay empty (no QA host in reusable defaults).');
19
+ }
20
+
21
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cforge-env-'));
22
+ const envFile = path.join(tmpDir, '.env.qa');
23
+ process.env.KEEP_ME = 'original';
24
+ await fs.writeFile(envFile, 'KEEP_ME=overridden\nNEW_QA_VAR=from-file\n# comment\nexport OTHER=1\n');
25
+ loadEnvFile(envFile);
26
+ if (process.env.KEEP_ME !== 'original') {
27
+ throw new Error('❌ loadEnvFile overrode an existing environment variable.');
28
+ }
29
+ if (resolveEnv('NEW_QA_VAR') !== 'from-file') {
30
+ throw new Error('❌ loadEnvFile did not load NEW_QA_VAR.');
31
+ }
32
+
33
+ validateAuthConfig({ auth: { provider: 'none' } });
34
+ validateAuthConfig({ auth: { provider: 'keycloak', useMockAuth: true } });
35
+
36
+ let liveFailed = false;
37
+ try {
38
+ validateAuthConfig({ auth: { provider: 'keycloak', useMockAuth: false, realm: 'x', clientId: 'y' } });
39
+ } catch (err) {
40
+ liveFailed = err instanceof AuthConfigError && err.message.includes('auth.serverUrl');
41
+ }
42
+ if (!liveFailed) {
43
+ throw new Error('❌ Live Keycloak config must require auth.serverUrl.');
44
+ }
45
+
46
+ let missingEnvFailed = false;
47
+ delete process.env.KEYCLOAK_ADMIN_USERNAME;
48
+ delete process.env.KEYCLOAK_ADMIN_PASSWORD;
49
+ try {
50
+ validateAuthConfig(
51
+ {
52
+ auth: {
53
+ provider: 'keycloak',
54
+ serverUrl: 'http://example.invalid',
55
+ realm: 'demo',
56
+ provision: {
57
+ enabled: true,
58
+ adapter: 'keycloak-admin-rest',
59
+ adminUsernameEnv: 'KEYCLOAK_ADMIN_USERNAME',
60
+ adminPasswordEnv: 'KEYCLOAK_ADMIN_PASSWORD',
61
+ },
62
+ },
63
+ },
64
+ { requireProvision: true }
65
+ );
66
+ } catch (err) {
67
+ missingEnvFailed = err instanceof AuthConfigError && err.message.includes('KEYCLOAK_ADMIN_USERNAME');
68
+ if (JSON.stringify(err.message).includes('crud')) {
69
+ throw new Error('❌ Auth errors must not include secret values.');
70
+ }
71
+ }
72
+ if (!missingEnvFailed) {
73
+ throw new Error('❌ Provision validation must name the missing KEYCLOAK_ADMIN_USERNAME env var.');
74
+ }
75
+
76
+ const stripped = stripSecretsForGenerate({
77
+ clientSecret: 'super-secret',
78
+ provision: { users: [{ username: 'a', password: 'nope', passwordEnv: 'X' }] },
79
+ });
80
+ if (stripped.clientSecret || stripped.provision.users[0].password) {
81
+ throw new Error('❌ stripSecretsForGenerate left secrets in place.');
82
+ }
83
+
84
+ const generateJs = await fs.readFile(path.join(__dirname, '..', 'src/commands/generate.js'), 'utf8');
85
+ const generatorJs = await fs.readFile(path.join(__dirname, '..', 'src/generator/index.js'), 'utf8');
86
+ if (!generateJs.includes('stripSecretsForGenerate') || !generatorJs.includes('stripSecretsForGenerate')) {
87
+ throw new Error('❌ generate CLI and generator engine must strip secrets before writing templates.');
88
+ }
89
+
90
+ const { parseJdl } = require('../src/parser/jdl-parser');
91
+ const { generate } = require('../src/generator');
92
+ const secretOut = path.join(tmpDir, 'secret-gen');
93
+ const genConfig = JSON.parse(JSON.stringify(defaultValues));
94
+ genConfig.output.directory = secretOut;
95
+ genConfig.api.microservice = 'book-service';
96
+ genConfig.auth = {
97
+ provider: 'keycloak',
98
+ useMockAuth: true,
99
+ clientSecret: 'MUST-NOT-EMIT-SECRET',
100
+ provision: {
101
+ enabled: false,
102
+ users: [{ username: 'seed', password: 'MUST-NOT-EMIT-PASSWORD', passwordEnv: 'X' }],
103
+ },
104
+ };
105
+ const parsed = parseJdl('entity Book { name String }', genConfig);
106
+ await generate(parsed, genConfig);
107
+ const leaked = [];
108
+ async function scan(dir) {
109
+ const entries = await fs.readdir(dir, { withFileTypes: true });
110
+ for (const entry of entries) {
111
+ const full = path.join(dir, entry.name);
112
+ if (entry.isDirectory()) {
113
+ await scan(full);
114
+ continue;
115
+ }
116
+ const text = await fs.readFile(full, 'utf8');
117
+ if (text.includes('MUST-NOT-EMIT-SECRET') || text.includes('MUST-NOT-EMIT-PASSWORD')) {
118
+ leaked.push(path.relative(secretOut, full));
119
+ }
120
+ }
121
+ }
122
+ await scan(secretOut);
123
+ if (leaked.length) {
124
+ throw new Error(`❌ Generator emitted secrets into: ${leaked.join(', ')}`);
125
+ }
126
+
127
+ const derived = deriveAuthUrls({ serverUrl: 'http://kc.example/', realm: 'CRUD-FORDGE' });
128
+ if (derived.issuerUrl !== 'http://kc.example/realms/CRUD-FORDGE') {
129
+ throw new Error('❌ deriveAuthUrls did not normalize issuerUrl.');
130
+ }
131
+
132
+ const fuseRuntimeSrc = await fs.readFile(path.join(__dirname, '..', 'src/generator/fuse-runtime.js'), 'utf8');
133
+ if (!fuseRuntimeSrc.includes("parsed['/realms']") || !fuseRuntimeSrc.includes('/services/keycloak-admin')) {
134
+ throw new Error('❌ Live Keycloak proxy must wire /realms + /services/keycloak-admin (middleware).');
135
+ }
136
+ if (fuseRuntimeSrc.includes("parsed['/admin/realms'] =")) {
137
+ throw new Error('❌ Live proxy must not assign /admin/realms (native Keycloak); use Admin Service /api/v1.');
138
+ }
139
+
140
+ const { applyFuseRuntimePack, normalizeServices, resolveProxyFiles } = require('../src/generator/fuse-runtime');
141
+ const normalized = normalizeServices({
142
+ identityaccessmodule: 'http://62.84.188.166:30084',
143
+ 'book-service': { prod: 'http://prod:1', dev: 'http://dev:2', pathRewrite: '/book-service' },
144
+ });
145
+ if (normalized.length !== 2 || normalized[0].proxyPath !== '/services/identityaccessmodule') {
146
+ throw new Error('❌ normalizeServices failed for string service entries.');
147
+ }
148
+ if (resolveProxyFiles('prod').join() !== 'proxy.conf.json' || resolveProxyFiles('dev').join() !== 'proxy.dev.conf.json') {
149
+ throw new Error('❌ resolveProxyFiles must map prod→proxy.conf.json and dev→proxy.dev.conf.json.');
150
+ }
151
+
152
+ const runtimeOut = path.join(tmpDir, 'runtime-gen');
153
+ await fs.copy(path.join(__dirname, '..', 'fuse-starter-pack/fuse-v21.1/src/environments'), path.join(runtimeOut, 'src/environments'));
154
+ await fs.copy(path.join(__dirname, '..', 'fuse-starter-pack/fuse-v21.1/proxy.conf.json'), path.join(runtimeOut, 'proxy.conf.json'));
155
+ await fs.copy(path.join(__dirname, '..', 'fuse-starter-pack/fuse-v21.1/proxy.dev.conf.json'), path.join(runtimeOut, 'proxy.dev.conf.json'));
156
+ const runtimeConfig = {
157
+ fuse: { enabled: true },
158
+ api: {
159
+ baseUrl: '/api',
160
+ services: { identityaccessmodule: 'http://62.84.188.166:30084' },
161
+ },
162
+ proxy: { write: 'prod' },
163
+ auth: {
164
+ provider: 'keycloak',
165
+ serverUrl: 'http://62.84.188.166:31740',
166
+ realm: 'CRUD-FORDGE',
167
+ clientId: 'crudforge-client-QA',
168
+ useMockAuth: false,
169
+ forwardedProto: 'https',
170
+ provision: { adminServiceUrl: 'http://62.84.188.166:31740/' },
171
+ },
172
+ };
173
+ applyFuseRuntimePack({
174
+ outputDir: runtimeOut,
175
+ config: runtimeConfig,
176
+ writeOrPreview: (file, content) => fs.writeFileSync(file, content),
177
+ });
178
+ const envText = await fs.readFile(path.join(runtimeOut, 'src/environments/environment.ts'), 'utf8');
179
+ if (!envText.includes("kcClientId: 'crudforge-client-QA'") || !envText.includes('identityaccessmodule')) {
180
+ throw new Error('❌ Fuse runtime did not write Keycloak + services into environment.ts.');
181
+ }
182
+ if (envText.includes('clientSecret') || envText.includes('PASSWORD') || envText.includes(':30080')) {
183
+ throw new Error('❌ Fuse runtime must not emit secrets or native Keycloak :30080.');
184
+ }
185
+ const prodProxy = JSON.parse(await fs.readFile(path.join(runtimeOut, 'proxy.conf.json'), 'utf8'));
186
+ const devProxy = JSON.parse(await fs.readFile(path.join(runtimeOut, 'proxy.dev.conf.json'), 'utf8'));
187
+ if (!prodProxy['/services/identityaccessmodule'] || !prodProxy['/realms']) {
188
+ throw new Error('❌ prod proxy missing service or /realms entries.');
189
+ }
190
+ if (prodProxy['/admin/realms'] || String(JSON.stringify(prodProxy)).includes(':30080')) {
191
+ throw new Error('❌ prod proxy must not use /admin/realms or Keycloak :30080.');
192
+ }
193
+ if (!prodProxy['/services/keycloak-admin'] || !String(prodProxy['/services/keycloak-admin'].target || '').includes('31740')) {
194
+ throw new Error('❌ prod proxy missing Keycloak admin-service (/services/keycloak-admin → :31740).');
195
+ }
196
+ if (!String(prodProxy['/realms'].target || '').includes('31740')) {
197
+ throw new Error('❌ prod /realms must target Admin Service :31740.');
198
+ }
199
+ if (!envText.includes("kcAdminServiceUrl: '/services/keycloak-admin'") || !envText.includes('useKeycloakAdminService: true')) {
200
+ throw new Error('❌ environment.ts must expose relative kcAdminServiceUrl + useKeycloakAdminService.');
201
+ }
202
+ if (devProxy['/services/identityaccessmodule']) {
203
+ throw new Error('❌ proxy.write=prod must not update proxy.dev.conf.json services.');
204
+ }
205
+
206
+ // Developer generate via config file (preferences) must wire the same proxies.
207
+ const { generateFromSource } = require('../src/index');
208
+ const cfgGenOut = path.join(tmpDir, 'cfg-gen-app');
209
+ await fs.ensureDir(cfgGenOut);
210
+ const prefsPath = path.join(tmpDir, 'dev-keycloak.yml');
211
+ await fs.writeFile(
212
+ prefsPath,
213
+ `
214
+ fuse:
215
+ enabled: true
216
+ cleanLayout: true
217
+ starterPackPath: "${path.join(__dirname, '..', 'fuse-starter-pack', 'fuse-v21.1').replace(/\\/g, '/')}"
218
+ output:
219
+ directory: "${cfgGenOut.replace(/\\/g, '/')}"
220
+ overwritePolicy: overwrite
221
+ proxy:
222
+ write: both
223
+ auth:
224
+ provider: keycloak
225
+ serverUrl: http://62.84.188.166:31740
226
+ realm: CRUD-FORDGE
227
+ clientId: crudforge-client-QA
228
+ useMockAuth: false
229
+ forwardedProto: https
230
+ provision:
231
+ enabled: false
232
+ adapter: admin-service
233
+ adminServiceUrl: http://62.84.188.166:31740/
234
+ api:
235
+ baseUrl: /api
236
+ microservice: book-service
237
+ `.trim()
238
+ );
239
+ process.env.KEYCLOAK_ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USERNAME || 'crud';
240
+ process.env.KEYCLOAK_ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD || 'crud';
241
+ const fixture = await fs.readFile(path.join(__dirname, 'fixtures/mock.jdl'), 'utf8');
242
+ await generateFromSource(fixture, {
243
+ filePath: path.join(__dirname, 'fixtures/mock.jdl'),
244
+ configPath: prefsPath,
245
+ outputDirectory: cfgGenOut,
246
+ fuse: { enabled: true, cleanLayout: true },
247
+ auth: {
248
+ provider: 'keycloak',
249
+ useMockAuth: false,
250
+ serverUrl: 'http://62.84.188.166:31740',
251
+ realm: 'CRUD-FORDGE',
252
+ clientId: 'crudforge-client-QA',
253
+ forwardedProto: 'https',
254
+ provision: { enabled: false, adapter: 'admin-service', adminServiceUrl: 'http://62.84.188.166:31740/' },
255
+ },
256
+ proxy: { write: 'both' },
257
+ });
258
+ const cfgProxy = JSON.parse(await fs.readFile(path.join(cfgGenOut, 'proxy.conf.json'), 'utf8'));
259
+ const cfgDevProxy = JSON.parse(await fs.readFile(path.join(cfgGenOut, 'proxy.dev.conf.json'), 'utf8'));
260
+ const cfgEnv = await fs.readFile(path.join(cfgGenOut, 'src/environments/environment.ts'), 'utf8');
261
+ for (const proxy of [cfgProxy, cfgDevProxy]) {
262
+ if (!proxy['/realms'] || !proxy['/services/keycloak-admin']) {
263
+ throw new Error('❌ Config-file Keycloak generate missing /realms or /services/keycloak-admin in proxy.');
264
+ }
265
+ if (proxy['/admin/realms'] || String(JSON.stringify(proxy)).includes(':30080')) {
266
+ throw new Error('❌ Config-file generate must not proxy native Keycloak :30080.');
267
+ }
268
+ if (!String(proxy['/realms'].target).includes('31740') || !String(proxy['/services/keycloak-admin'].target).includes('31740')) {
269
+ throw new Error('❌ Config-file Keycloak generate proxy targets must be Admin Service :31740.');
270
+ }
271
+ }
272
+ if (!cfgEnv.includes('useMockAuth: false') || !cfgEnv.includes("kcAdminServiceUrl: '/services/keycloak-admin'")) {
273
+ throw new Error('❌ Config-file Keycloak generate environment.ts is not live-wired.');
274
+ }
275
+
276
+ const bootstrap = require('../src/provision/bootstrap');
277
+ if (typeof bootstrap.runBootstrap !== 'function' || typeof bootstrap.runCleanup !== 'function') {
278
+ throw new Error('❌ src/provision/bootstrap.js must export runBootstrap and runCleanup.');
279
+ }
280
+
281
+ const exampleEnv = path.join(__dirname, '..', '.env.qa.example');
282
+ if (!fs.existsSync(exampleEnv)) {
283
+ throw new Error('❌ Missing committed .env.qa.example');
284
+ }
285
+ const exampleText = await fs.readFile(exampleEnv, 'utf8');
286
+ if (exampleText.includes('=crud') && exampleText.match(/PASSWORD=.+/)) {
287
+ const assigned = exampleText.split('\n').filter((line) => /PASSWORD=.+/.test(line) && !line.includes('your-'));
288
+ if (assigned.some((line) => !line.includes('your-') && !line.includes('change-me') && line.split('=')[1])) {
289
+ // placeholders are fine
290
+ }
291
+ }
292
+
293
+ await fs.remove(tmpDir);
294
+ console.log('✅ Auth provision configuration, env refs, and bootstrap exports validated.');
295
+ }
296
+
297
+ module.exports = { runAuthProvisionChecks };
@@ -0,0 +1,145 @@
1
+ const assert = require('node:assert/strict');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const vm = require('node:vm');
5
+ const { stripTypeScriptTypes } = require('node:module');
6
+ const { validateFieldPreferences, entityFieldSettings } = require('../src/config/field-preferences');
7
+ const { validateConfig } = require('../src/config/load-config');
8
+
9
+ function load(name, exported, context = {}) {
10
+ let source = fs.readFileSync(path.join(__dirname, '../src/templates/angular', name), 'utf8')
11
+ .replace(/^import[\s\S]*?;\n/gm, '').replace(/@Directive\(\{[\s\S]*?\n\}\)\n/, '')
12
+ .replace(/@Input\(\) /g, '').replace(/^@Injectable.*\n/gm, '').replace(/^export /gm, '');
13
+ const module = { exports: {} };
14
+ vm.runInNewContext(stripTypeScriptTypes(source) + `\nmodule.exports = ${exported};`, {module, ...context});
15
+ return module.exports;
16
+ }
17
+ const entity = { name: 'Province', fields: [
18
+ {fieldName:'code',fieldType:'String'}, {fieldName:'description',fieldType:'TextBlob'},
19
+ {fieldName:'count',fieldType:'Integer'}, {fieldName:'state',fieldType:'State',fieldTypeIsEnum:true},
20
+ {fieldName:'createdBy',fieldType:'String'},
21
+ ] };
22
+ const config = format => ({fields:{entities:{Province:{fields:{code:{inputFormat:format}}}}}});
23
+ for(const format of ['none','uppercase','lowercase']) validateFieldPreferences(config(format),[entity],'/example/config.yml');
24
+ assert.throws(()=>validateConfig(config('titlecase'),'/example/config.yml'), /config.yml.*code.inputFormat.*uppercase/);
25
+ assert.throws(()=>validateFieldPreferences(config('none'),[], '/example/config.yml'), /Province.*unknown entity/);
26
+ for(const field of ['count','state','createdBy','missing']) {
27
+ assert.throws(()=>validateFieldPreferences({fields:{entities:{Province:{fields:{[field]:{inputFormat:'uppercase'}}}}}},[entity]), /incompatible|unknown field/);
28
+ }
29
+ assert.equal(entityFieldSettings({}, entity).code.inputFormat,'none');
30
+ assert.equal(entityFieldSettings({formBehavior:{uppercaseInputs:true}},entity).code.inputFormat,'uppercase');
31
+ assert.equal(entityFieldSettings({...config('none'),formBehavior:{uppercaseInputs:true}},entity).code.inputFormat,'none');
32
+ const {formatInput,normalizeInputFields} = load('shared/base/input-format.ts.ejs','{formatInput, normalizeInputFields}');
33
+ const original = {code:'hello world',description:null,count:4};
34
+ const normalized = normalizeInputFields(original,{code:{inputFormat:'uppercase'},description:{inputFormat:'lowercase'}});
35
+ assert.equal(normalized.code,'HELLO WORLD');assert.equal(original.code,'hello world');assert.equal(normalized.description,null);
36
+ assert.equal(Object.hasOwn(normalizeInputFields({}, {code:{inputFormat:'uppercase'}}),'code'),false);
37
+ const Directive = load('shared/base/input-format.directive.ts.ejs','InputFormatDirective',{formatInput});
38
+ const input = {value:'aßbc',selectionStart:2,selectionEnd:2,selectionDirection:'none',setSelectionRange(start,end){this.selectionStart=start;this.selectionEnd=end;}};
39
+ const directive = Object.create(Directive.prototype);
40
+ let value;
41
+ Object.assign(directive,{element:{nativeElement:input},appInputFormat:'uppercase',composing:false,onChange:v=>{value=v;}});
42
+ directive.handleInput();assert.equal(value,'ASSBC');assert.equal(input.selectionStart,3);
43
+ input.value='composing';directive.composing=true;directive.handleInput();assert.equal(input.value,'composing');
44
+ directive.endComposition();assert.equal(value,'COMPOSING');
45
+ directive.appInputFormat='lowercase';input.value='HELLO';directive.handleInput();assert.equal(value,'hello');
46
+ directive.appInputFormat='none';input.value='Keep Case';directive.handleInput();assert.equal(value,'Keep Case');
47
+ const mapUser=load('auth/current-user.ts.ejs','mapCurrentUser');
48
+ assert.equal(mapUser(null),null);
49
+ assert.equal(mapUser({sub:'a',given_name:'Ada',family_name:'Lovelace',email:'ada@example.test'}).name,'Ada Lovelace');
50
+ assert.equal(mapUser({preferred_username:'username'}).name,'username');
51
+ assert.equal(mapUser({name:'First Last',picture:'javascript:bad'}).avatar,'');
52
+ assert.equal(mapUser({name:'First Last',picture:'https://example.test/avatar.png'}).avatar,'https://example.test/avatar.png');
53
+ assert.equal(mapUser({name:'First Last'}).initials,'FL');
54
+ const Utils=load('auth/auth.utils.ts.ejs','AuthUtils',{atob,TextDecoder,Uint8Array});
55
+ const token='x.'+Buffer.from(JSON.stringify({name:'අබ්දුල් José'})).toString('base64url')+'.x';
56
+ assert.equal(Utils.decodePayload(token).name,'අබ්දුල් José');
57
+ console.log('Field preferences/profile tests passed: validation, precedence, payloads, cursor, IME, mappings, UTF-8 claims.');
58
+
59
+ for (const [parse, blueprint] of [
60
+ [require('../src/parser/gdl-parser').parseGdl, '@Audited entity Province { string code }'],
61
+ [require('../src/parser/jdl-parser').parseJdl, '@EnableAudit entity Province { code String }'],
62
+ ]) {
63
+ const names = ['createdBy','createdDate','lastModifiedBy','lastModifiedDate','updatedBy','createdAt'];
64
+ const fields = parse(blueprint, {fields:{audit:{enabled:true,names}}}).entities[0].fields;
65
+ for(const name of ['createdBy','lastModifiedBy','updatedBy']) assert.equal(fields.find(f=>f.fieldName===name).fieldType,'String');
66
+ for(const name of ['createdDate','lastModifiedDate','createdAt']) assert.equal(fields.find(f=>f.fieldName===name).fieldType,'Instant');
67
+ }
68
+
69
+ {
70
+ const { parseGdl } = require('../src/parser/gdl-parser');
71
+ const { parseJdl } = require('../src/parser/jdl-parser');
72
+ const { analyzeSchema } = require('../src/validator/diagnostics');
73
+
74
+ const gdl = parseGdl('@Audited @Searchable @ActiveStatus\nentity Region {\n string name\n}');
75
+ const region = gdl.entities[0];
76
+ assert.equal(region.searchable, true);
77
+ assert.equal(region.activeStatus, true);
78
+ assert.equal(region.audited, true);
79
+ assert.equal(region.fields.find((f) => f.fieldName === 'isActive').fieldType, 'Boolean');
80
+ assert.ok(region.fields.some((f) => f.fieldName === 'createdBy'));
81
+
82
+ const withExplicit = parseGdl('@ActiveStatus\nentity Outlet {\n bool isActive\n string code\n}');
83
+ assert.equal(withExplicit.entities[0].fields.filter((f) => f.fieldName === 'isActive').length, 1);
84
+
85
+ const jdl = parseJdl('@EnableAudit @Searchable @ActiveStatus\nentity Region {\n name String\n}');
86
+ assert.equal(jdl.entities[0].fields.find((f) => f.fieldName === 'isActive').fieldType, 'Boolean');
87
+ assert.equal(jdl.entities[0].searchable, true);
88
+
89
+ const ok = analyzeSchema(
90
+ '@Audited @Searchable @ActiveStatus\nentity Region {\n string name\n}',
91
+ { isGdl: true, file: 'region.gdl' },
92
+ );
93
+ assert.equal(ok.diagnostics.filter((d) => d.severity === 'error').length, 0);
94
+ assert.ok(ok.parsed.entities[0].fields.some((f) => f.fieldName === 'isActive'));
95
+
96
+ const soft = analyzeSchema('@SoftDelete entity A {\n string a\n}', { isGdl: true, file: 'a.gdl' });
97
+ assert.ok(soft.diagnostics.some((d) => d.code === 'unsupported-annotation'));
98
+ }
99
+
100
+ (async () => {
101
+ const os = require('node:os');
102
+ const { generateFromSource } = require('../src');
103
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'crudforge-field-validation-'));
104
+ try {
105
+ const configPath = path.join(directory, 'config.json');
106
+ const output = path.join(directory, 'output');
107
+ fs.writeFileSync(configPath, JSON.stringify({ ...config('uppercase'), output: {directory:output} }));
108
+ await assert.rejects(generateFromSource('entity Province { count Integer }', {configPath}), error =>
109
+ error.message.includes(configPath) && error.message.includes('fields.entities.Province.fields.code.inputFormat') && error.message.includes('unknown field'));
110
+ assert.equal(fs.existsSync(output), false, 'Reject invalid field configuration before generating any files');
111
+ } finally { fs.rmSync(directory, {recursive:true,force:true}); }
112
+ })().catch(error => { console.error(error); process.exitCode=1; });
113
+
114
+ // A late refresh from account A must not restore its tokens/profile after switching to B.
115
+ {
116
+ const rx = require('rxjs');
117
+ const Auth = load('auth/auth.service.ts.ejs', 'AuthService', {
118
+ ...rx, environment: { useMockAuth:false, kcClientId:'test' }, URLSearchParams,
119
+ HttpErrorResponse: class extends Error {},
120
+ });
121
+ const auth = Object.create(Auth.prototype);
122
+ const storage = new Map([['refreshToken','account-a-refresh']]);
123
+ const response = new rx.Subject();
124
+ let error, stores = 0;
125
+ Object.assign(auth, {
126
+ authenticated:true, refreshing$:null,
127
+ getItem:key=>storage.get(key) || '', requestToken:()=>response,
128
+ storeTokens:()=>stores++,
129
+ });
130
+ auth.refreshAccessToken().subscribe({error:value=>{error=value;}});
131
+ storage.set('refreshToken','account-b-refresh');
132
+ response.next({access_token:'old-account-token',refresh_token:'old-account-refresh'});
133
+ assert.match(error.message,/Session changed/);
134
+ assert.equal(stores,0);
135
+ assert.equal(storage.get('refreshToken'),'account-b-refresh');
136
+ let cleared = false;
137
+ const failedResponse = new rx.Subject();
138
+ auth.requestToken = () => failedResponse;
139
+ auth.clearSession = () => { cleared = true; };
140
+ auth.isTerminalAuthError = () => true;
141
+ auth.refreshAccessToken().subscribe({error:()=>{}});
142
+ storage.set('refreshToken','account-c-refresh');
143
+ failedResponse.error(new Error('Previous account refresh rejected'));
144
+ assert.equal(cleared,false);
145
+ }
@@ -0,0 +1,7 @@
1
+ @Audited
2
+ entity Province {
3
+ string index
4
+ string provinceCode
5
+ string provinceName
6
+ bool isActive
7
+ }
@@ -0,0 +1,17 @@
1
+ @EnableAudit
2
+ entity Book {
3
+ title String required
4
+ description String
5
+ publishDate LocalDate required
6
+ pages Integer min(10) max(1000)
7
+ }
8
+
9
+ entity Author {
10
+ name String required
11
+ biography String
12
+ active Boolean
13
+ }
14
+
15
+ relationship OneToMany {
16
+ Author{book} to Book{author}
17
+ }
Binary file
@@ -0,0 +1,33 @@
1
+ project:
2
+ name: Field and profile regression fixture
3
+ output:
4
+ directory: ./e2e/.field-profile
5
+ overwritePolicy: overwrite
6
+ fuse:
7
+ enabled: true
8
+ cleanLayout: true
9
+ includePrebuilt: false
10
+ api:
11
+ microservice: pmb-reference-module
12
+ baseUrl: /api
13
+ mock:
14
+ enabled: false
15
+ fields:
16
+ entities:
17
+ Province:
18
+ fields:
19
+ provinceCode: { inputFormat: uppercase }
20
+ provinceName: { inputFormat: lowercase }
21
+ index: { inputFormat: none }
22
+ auth:
23
+ provider: keycloak
24
+ realm: test
25
+ clientId: test-client
26
+ # Deliberately non-live: Playwright intercepts every auth and API request.
27
+ serverUrl: http://127.0.0.1:9999
28
+ useMockAuth: false
29
+ provision:
30
+ enabled: false
31
+ adminServiceUrl: http://127.0.0.1:9999
32
+ iam:
33
+ enabled: false