@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,55 @@
1
+ // Serial measurements. Cold means empty Angular disk cache, not an OS cache flush.
2
+ const fs=require('fs-extra'),path=require('path'),os=require('os');
3
+ const {spawn}=require('child_process');
4
+ const {performance}=require('perf_hooks');
5
+ const {chromium}=require('@playwright/test');
6
+ const root=path.resolve(process.argv[2]);
7
+ const results={machine:{platform:os.platform(),arch:os.arch(),cpus:os.cpus().length,node:process.version},commands:{build:'node node_modules/@angular/cli/bin/ng.js build --configuration production --stats-json',serve:'node node_modules/@angular/cli/bin/ng.js serve --host 127.0.0.1 --port 4301',workers:2},fixtures:{}};
8
+ function command(cwd,args,log){return new Promise((resolve,reject)=>{const start=performance.now();const stream=fs.createWriteStream(log);const child=spawn(process.execPath,['node_modules/@angular/cli/bin/ng.js',...args],{cwd,env:{...process.env,NG_BUILD_MAX_WORKERS:'2'},stdio:['ignore','pipe','pipe']});child.stdout.pipe(stream);child.stderr.pipe(stream);child.on('error',reject);child.on('exit',code=>{stream.end();code===0?resolve((performance.now()-start)/1000):reject(new Error(`Command failed (${code}), see ${log}`));});});}
9
+ async function browserMetrics(url){
10
+ const browser=await chromium.launch({channel:process.env.CRUDFORGE_BROWSER_CHANNEL||'chrome'});
11
+ const runs=[];
12
+ try { for(let i=0;i<3;i++) {
13
+ const context=await browser.newContext();const page=await context.newPage();let reads=0;const errors=[];page.on('pageerror',e=>errors.push(e.message));
14
+ await page.route('**/*',async route=>{const u=new URL(route.request().url());
15
+ if(u.pathname.endsWith('/token')){const token='test.'+Buffer.from(JSON.stringify({sub:'benchmark',name:'Benchmark User',email:'benchmark@example.test',exp:Math.floor(Date.now()/1000)+3600,realm_access:{roles:['ROLE_ADMIN']}})).toString('base64url')+'.test';return route.fulfill({json:{access_token:token,refresh_token:'benchmark-refresh',expires_in:3600}});}
16
+ if(u.pathname.includes('/services/keycloak-admin/')) return route.fulfill({json:{id:'benchmark',attributes:{province:['VIEW']}}});
17
+ if(u.pathname.includes('/api/provinces')){reads++;return route.fulfill({headers:{'X-Total-Count':'100'},json:[{id:1,index:'1',provinceCode:'AB',provinceName:'Benchmark Province',isActive:true}]});}
18
+ if(u.hostname!=='127.0.0.1')return route.abort();return route.continue();
19
+ });
20
+ let t=performance.now();await page.goto(url+'/auth/sign-in',{waitUntil:'domcontentloaded'});
21
+ await page.waitForFunction(()=>{const e=document.querySelector('auth-sign-in');return !!e&&!!window.ng?.getComponent(e);});
22
+ const interactiveMs=performance.now()-t;
23
+ const fcpMs=await page.evaluate(()=>performance.getEntriesByName('first-contentful-paint')[0]?.startTime??null);
24
+ await page.getByLabel('Username or email').fill('benchmark');await page.getByLabel('Password',{exact:true}).fill('test');await page.getByRole('button',{name:'Sign in',exact:true}).click();await page.waitForURL(/\/admin/);
25
+ t=performance.now();await page.goto(url+'/admin/province');await page.getByRole('cell',{name:'Benchmark Province',exact:true}).waitFor();const entityLoadMs=performance.now()-t;
26
+ const firstReads=reads;await page.getByRole('button',{name:'Next page'}).click();await page.waitForTimeout(500);const paginationReads=reads-firstReads;
27
+ t=performance.now();await page.reload();await page.getByRole('cell',{name:'Benchmark Province',exact:true}).waitFor();const reloadMs=performance.now()-t;
28
+ runs.push({interactiveMs,fcpMs,entityLoadMs,reloadMs,paginationReads,errors});await context.close();
29
+ }}finally{await browser.close();}return runs;
30
+ }
31
+ (async()=>{
32
+ for(const size of ['small','large']){
33
+ const cwd=path.join(root,size),metric={};results.fixtures[size]=metric;
34
+ await fs.remove(path.join(cwd,'.angular/cache'));await fs.remove(path.join(cwd,'dist'));
35
+ metric.coldBuildSeconds=await command(cwd,['build','--configuration','production','--stats-json'],path.join(root,size+'-cold.log'));
36
+ metric.warmBuildSeconds=await command(cwd,['build','--configuration','production','--stats-json'],path.join(root,size+'-warm.log'));
37
+ const browserDir=path.join(cwd,'dist/fuse/browser');const html=await fs.readFile(path.join(browserDir,fs.existsSync(path.join(browserDir,'index.html'))?'index.html':'index.csr.html'),'utf8');
38
+ const entries=[...new Set([...html.matchAll(/(?:src|href)="([^"?#]+\.(?:js|css))"/g)].map(m=>m[1]))];
39
+ metric.initialBytes=entries.reduce((n,f)=>n+fs.statSync(path.join(browserDir,f)).size,0);
40
+ metric.initialFiles=entries;metric.serverBundle=await fs.pathExists(path.join(cwd,'dist/fuse/server'));
41
+ await fs.remove(path.join(cwd,'.angular/cache'));
42
+ const started=performance.now();let output='';const log=fs.createWriteStream(path.join(root,size+'-serve.log'));
43
+ const server=spawn(process.execPath,['node_modules/@angular/cli/bin/ng.js','serve','--host','127.0.0.1','--port','4301'],{cwd,env:{...process.env,NG_BUILD_MAX_WORKERS:'2'},stdio:['ignore','pipe','pipe']});
44
+ server.stdout.on('data',b=>{output+=b.toString();log.write(b);});server.stderr.on('data',b=>{output+=b.toString();log.write(b);});
45
+ const wait=async predicate=>{const deadline=Date.now()+180000;while(!predicate()){if(server.exitCode!==null||Date.now()>deadline)throw new Error('Serve/rebuild failed: '+output.slice(-2000));await new Promise(r=>setTimeout(r,100));}};
46
+ try{
47
+ await wait(()=>output.includes('Local:'));metric.coldDevStartupSeconds=(performance.now()-started)/1000;
48
+ metric.browser=await browserMetrics('http://127.0.0.1:4301');
49
+ const apps=path.join(cwd,'src/app/domains/admin/modules/apps');const folder=fs.existsSync(path.join(apps,'micro-functions'))?'micro-functions':'microservice';const file=path.join(apps,folder,'province/components/presentation/list/province-list.component.html');const original=await fs.readFile(file,'utf8');
50
+ output='';const rebuild=performance.now();await fs.writeFile(file,original.replace('Province Management','Province Management Benchmark'));
51
+ try{await wait(()=>output.includes('Application bundle generation complete'));metric.rebuildSeconds=(performance.now()-rebuild)/1000;}finally{await fs.writeFile(file,original);}
52
+ }finally{server.kill('SIGTERM');await new Promise(resolve=>server.once('exit',resolve));log.end();}
53
+ await fs.writeJson(path.join(root,'measurements.json'),results,{spaces:2});console.log(size,JSON.stringify(metric));
54
+ }
55
+ })().catch(e=>{console.error(e);process.exitCode=1;});
@@ -0,0 +1,9 @@
1
+ const path = require('path');
2
+
3
+ try {
4
+ const branding = require('../src/utils/branding');
5
+ branding.printBanner();
6
+ } catch (e) {
7
+ // Gracefully suppress any exceptions to avoid breaking or blocking npm install
8
+ }
9
+ process.exit(0);
@@ -0,0 +1,34 @@
1
+ const fs = require("fs-extra"),
2
+ path = require("path");
3
+ const { generateFromSource } = require("../src");
4
+ (async () => {
5
+ if (!process.argv[2])
6
+ throw Error(
7
+ "Usage: node scripts/prepare-quality-apps.js <new-output-directory>",
8
+ );
9
+ const root = path.resolve(process.argv[2]);
10
+ if (await fs.pathExists(root))
11
+ throw Error("Use a new output directory to preserve existing work.");
12
+ const fixture = path.resolve(
13
+ __dirname,
14
+ "../test/fixtures/quality/representative.gdl",
15
+ );
16
+ for (const kind of ["go-duck", "jhipster", "custom"]) {
17
+ const output = path.join(root, kind);
18
+ await generateFromSource(await fs.readFile(fixture, "utf8"), {
19
+ filePath: fixture,
20
+ outputDirectory: output,
21
+ configPath: path.resolve(
22
+ __dirname,
23
+ "../test/fixtures/quality/" + kind + ".yml",
24
+ ),
25
+ });
26
+ await fs.outputFile(
27
+ path.join(output, "gdl/example.gdl"),
28
+ await fs.readFile(fixture, "utf8"),
29
+ );
30
+ }
31
+ })().catch((error) => {
32
+ console.error(error);
33
+ process.exitCode = 1;
34
+ });
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { spawnSync } = require('child_process');
5
+ const YAML = require('yaml');
6
+
7
+ const root = path.resolve(__dirname, '..');
8
+ const mode = process.argv[2] || 'ci';
9
+ const area = path.resolve(process.env.CRUDFORGE_TESTING_AREA ||
10
+ '/Users/abdulla/Work/CRUD-FORDGE-SETUP/crud-fordge-testing-area');
11
+
12
+ function run(command, args, cwd) {
13
+ const result = spawnSync(command, args, { cwd, stdio: 'inherit' });
14
+ if (result.error) throw result.error;
15
+ if (result.status !== 0) throw new Error(`${command} failed (${result.signal || result.status})`);
16
+ }
17
+
18
+ async function main() {
19
+ if (mode === '--help' || mode === '-h') {
20
+ console.log('Usage: ./run-config-tests.sh [validate|generate|ci]\n' +
21
+ 'Default ci: validate GDL, run generator tests, generate app, install dependencies, build development.\n' +
22
+ 'Set CRUDFORGE_TESTING_AREA to override the testing directory.\n' +
23
+ 'New projects: <testing-area>/crudforge-app/config/crudforge.config.yml.\n' +
24
+ 'Set CRUDFORGE_PROJECT_DIR to select another initialized project.');
25
+ return;
26
+ }
27
+ if (!['validate', 'generate', 'ci'].includes(mode)) throw new Error(`Unknown mode: ${mode}`);
28
+ const project = path.resolve(process.env.CRUDFORGE_PROJECT_DIR || path.join(area, 'crudforge-app'));
29
+ const { loadConfig } = require('../src/config/load-config');
30
+ const { configPath } = await loadConfig(project, null, { required: true, projectPaths: true });
31
+ // Require the explicit input and output settings for this QA workflow.
32
+ const config = YAML.parse(fs.readFileSync(configPath, 'utf8'));
33
+ if (!config?.input?.file || !config?.output?.directory || !config?.api?.microservice) {
34
+ throw new Error('Config requires input.file, output.directory, and api.microservice.');
35
+ }
36
+ if (config.dryRun || config.validation === false || config.fuse?.enabled !== true) {
37
+ throw new Error('Testing requires dryRun: false, validation: true, and fuse.enabled: true.');
38
+ }
39
+ const input = fs.realpathSync(path.resolve(project, config.input.file));
40
+ const output = path.resolve(project, config.output.directory);
41
+ if (output !== project) throw new Error('Project testing requires output.directory: ".".');
42
+ if (fs.lstatSync(project).isSymbolicLink()) throw new Error('The project directory must not be a symbolic link.');
43
+ const { parseGdl } = require('../src/parser/gdl-parser');
44
+ if (!input.endsWith('.gdl') || !parseGdl(fs.readFileSync(input, 'utf8'), config).entities.length) {
45
+ throw new Error('input.file must be a GDL file containing at least one entity.');
46
+ }
47
+ console.log(`Testing area: ${area}\nConfig: ${configPath}\nSchema: ${input}\nOutput: ${output}`);
48
+ run(process.execPath, [path.join(root, 'cli.js'), 'validate', input], project);
49
+ if (mode === 'validate') return;
50
+ if (mode === 'ci') run('npm', ['test'], root);
51
+ run(process.execPath, [path.join(root, 'cli.js'), 'generate', '--preferences', configPath, '--yes',
52
+ ...(process.env.CRUDFORGE_OVERWRITE_POLICY ? ['--overwrite-policy', process.env.CRUDFORGE_OVERWRITE_POLICY] : [])], project);
53
+ if (mode === 'ci') {
54
+ if (!fs.existsSync(path.join(output, 'node_modules/.bin/ng'))) {
55
+ run('npm', [fs.existsSync(path.join(output, 'package-lock.json')) ? 'ci' : 'install',
56
+ '--no-audit', '--no-fund'], output);
57
+ }
58
+ run('npm', ['run', 'build:dev'], output);
59
+ }
60
+ console.log(`${mode} passed. Generated app: ${output}`);
61
+ }
62
+
63
+ main().catch(error => { console.error(error.message); process.exitCode = 1; });
Binary file
Binary file
@@ -0,0 +1,92 @@
1
+ const path = require('path');
2
+ const colors = require('../utils/colors');
3
+ const { loadConfig } = require('../config/load-config');
4
+ const { loadEnvFile } = require('../config/env-refs');
5
+ const { deriveAuthUrls, validateAuthConfig } = require('../config/auth-config');
6
+
7
+ function loadRunBootstrap() {
8
+ try {
9
+ return require('../provision/bootstrap');
10
+ } catch (err) {
11
+ const message = err && err.message ? String(err.message) : '';
12
+ const missingProvision =
13
+ err &&
14
+ err.code === 'MODULE_NOT_FOUND' &&
15
+ (message.includes('provision/bootstrap') || message.includes('provision\\bootstrap'));
16
+ if (missingProvision) {
17
+ throw new Error(
18
+ 'Keycloak provisioning is not available: src/provision/bootstrap.js is missing. Implement the provision module before running `crudforge bootstrap`.'
19
+ );
20
+ }
21
+ throw err;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Thin CLI wrapper: load env + config, validate, then delegate to provision.
27
+ * Does not implement Keycloak mutations itself.
28
+ */
29
+ async function bootstrapCommand(options = {}) {
30
+ const cwd = process.cwd();
31
+
32
+ if (options.envFile) {
33
+ const envPath = path.isAbsolute(options.envFile)
34
+ ? options.envFile
35
+ : path.resolve(cwd, options.envFile);
36
+ loadEnvFile(envPath);
37
+ }
38
+
39
+ const { config, configPath } = await loadConfig(cwd, options.preferences);
40
+ if (configPath) {
41
+ console.log(colors.cyan(`🔵 Loaded configuration preferences from: ${configPath}`));
42
+ } else {
43
+ console.log(colors.dim('🔵 No configuration file found. Using default developer preferences.'));
44
+ }
45
+
46
+ config.auth = config.auth || {};
47
+ config.auth.provision = config.auth.provision || {};
48
+ if (process.env.CRUDFORGE_KC_SERVER_URL) {
49
+ config.auth.serverUrl = process.env.CRUDFORGE_KC_SERVER_URL;
50
+ }
51
+ if (process.env.CRUDFORGE_KC_REALM) {
52
+ config.auth.realm = process.env.CRUDFORGE_KC_REALM;
53
+ }
54
+ if (process.env.CRUDFORGE_KC_CLIENT_ID) {
55
+ config.auth.clientId = process.env.CRUDFORGE_KC_CLIENT_ID;
56
+ config.auth.provision.clientId = config.auth.provision.clientId || process.env.CRUDFORGE_KC_CLIENT_ID;
57
+ }
58
+ if (process.env.CRUDFORGE_KC_ADMIN_SERVICE_URL) {
59
+ config.auth.provision.adminServiceUrl = process.env.CRUDFORGE_KC_ADMIN_SERVICE_URL;
60
+ }
61
+ if (process.env.CRUDFORGE_KC_FORWARDED_PROTO) {
62
+ config.auth.forwardedProto = process.env.CRUDFORGE_KC_FORWARDED_PROTO;
63
+ }
64
+ if (options.dryRun) {
65
+ config.auth.provision.dryRun = true;
66
+ }
67
+
68
+ deriveAuthUrls(config.auth);
69
+ validateAuthConfig(config, { requireProvision: true });
70
+
71
+ const provision = loadRunBootstrap();
72
+ if (options.cleanup) {
73
+ if (typeof provision.runCleanup !== 'function') {
74
+ throw new Error('src/provision/bootstrap.js must export runCleanup(config, options).');
75
+ }
76
+ await provision.runCleanup(config, { preferences: options.preferences });
77
+ return;
78
+ }
79
+
80
+ if (typeof provision.runBootstrap !== 'function') {
81
+ throw new Error(
82
+ 'Keycloak provisioning is not available: src/provision/bootstrap.js must export runBootstrap(config, options).'
83
+ );
84
+ }
85
+
86
+ await provision.runBootstrap(config, {
87
+ dryRun: !!options.dryRun,
88
+ preferences: options.preferences,
89
+ });
90
+ }
91
+
92
+ module.exports = { bootstrapCommand };
@@ -0,0 +1,403 @@
1
+ const fs = require("fs-extra");
2
+ const path = require("path");
3
+ const net = require("net");
4
+ const http = require("http");
5
+ const https = require("https");
6
+ const { loadConfig } = require("../config/load-config");
7
+ const { analyzeSchema } = require("../validator/diagnostics");
8
+
9
+ function safeUrl(raw) {
10
+ try {
11
+ const u = new URL(raw);
12
+ return u.origin + u.pathname;
13
+ } catch {
14
+ return "[invalid URL]";
15
+ }
16
+ }
17
+ function probe(raw, timeoutMs = 3000) {
18
+ return new Promise((resolve) => {
19
+ let url;
20
+ try {
21
+ url = new URL(raw);
22
+ if (
23
+ !["http:", "https:"].includes(url.protocol) ||
24
+ url.username ||
25
+ url.password ||
26
+ url.search ||
27
+ url.hash
28
+ )
29
+ throw Error();
30
+ } catch {
31
+ return resolve({
32
+ status: "fail",
33
+ message:
34
+ "Use an HTTP(S) endpoint without credentials, query parameters or fragments.",
35
+ });
36
+ }
37
+ const req = (url.protocol === "https:" ? https : http).request(
38
+ url,
39
+ { method: "GET", headers: { Accept: "application/json" } },
40
+ (res) => {
41
+ res.resume();
42
+ const code = res.statusCode;
43
+ resolve({
44
+ status:
45
+ code < 400
46
+ ? "pass"
47
+ : code === 401 || code === 403
48
+ ? "warning"
49
+ : "fail",
50
+ message: `HTTP ${code}: ${code === 401 ? "authentication required" : code === 403 ? "access denied" : code >= 500 ? "server error" : code >= 400 ? "endpoint unavailable" : code >= 300 ? "redirect returned (not followed)" : "endpoint reachable; CRUD behavior unverified"}.`,
51
+ });
52
+ },
53
+ );
54
+ const timer = setTimeout(
55
+ () => req.destroy(new Error("timeout")),
56
+ timeoutMs,
57
+ );
58
+ req.on("error", (error) =>
59
+ resolve({
60
+ status: "fail",
61
+ message:
62
+ error.message === "timeout"
63
+ ? "Connection timed out."
64
+ : `Connection failed (${error.code || "transport error"}).`,
65
+ }),
66
+ );
67
+ req.on("close", () => clearTimeout(timer));
68
+ req.end();
69
+ });
70
+ }
71
+ function portAvailable(port) {
72
+ return new Promise((resolve) => {
73
+ const server = net.createServer();
74
+ server.once("error", () => resolve(false));
75
+ server.listen({ port, host: "127.0.0.1" }, () =>
76
+ server.close(() => resolve(true)),
77
+ );
78
+ });
79
+ }
80
+ async function diagnose({
81
+ cwd = process.cwd(),
82
+ preferences,
83
+ live = false,
84
+ nodeVersion = process.versions.node,
85
+ } = {}) {
86
+ const checks = [];
87
+ const add = (id, status, message, next = "") =>
88
+ checks.push({ id, status, message, next });
89
+ const cli = require("../../package.json");
90
+ add("cli", "pass", `CRUDForge ${cli.version}; Node ${nodeVersion}.`);
91
+ let loaded;
92
+ try {
93
+ loaded = await loadConfig(cwd, preferences, {
94
+ required: true,
95
+ projectPaths: true,
96
+ });
97
+ add("config", "pass", `Configuration: ${loaded.configPath}`);
98
+ } catch {
99
+ add(
100
+ "config",
101
+ "fail",
102
+ "Configuration is missing or malformed.",
103
+ "Run init or correct --preferences and YAML/JSON syntax; use validate for schema diagnostics.",
104
+ );
105
+ return { ok: false, checks };
106
+ }
107
+ const { config, projectRoot } = loaded;
108
+ const pkgPath = path.join(projectRoot, "package.json");
109
+ let pkg = null;
110
+ try {
111
+ if (await fs.pathExists(pkgPath)) pkg = await fs.readJson(pkgPath);
112
+ } catch {
113
+ add(
114
+ "package",
115
+ "fail",
116
+ "package.json is malformed.",
117
+ "Correct the JSON before installing dependencies.",
118
+ );
119
+ }
120
+ const requirement =
121
+ pkg?.engines?.node ||
122
+ (config.fuse?.enabled
123
+ ? require("../../fuse-starter-pack/fuse-v21.1/package.json").engines.node
124
+ : cli.engines.node);
125
+ const semver = require("semver");
126
+ if (!semver.validRange(requirement))
127
+ add(
128
+ "node",
129
+ "fail",
130
+ `Invalid Node requirement in package.json engines.`,
131
+ "Set a valid semver range.",
132
+ );
133
+ else {
134
+ const compatible = semver.satisfies(nodeVersion, requirement);
135
+ add(
136
+ "node",
137
+ compatible ? "pass" : "fail",
138
+ `Project requires Node ${requirement}; installed ${nodeVersion}.`,
139
+ compatible ? "" : "Select a compatible Node version and rerun doctor.",
140
+ );
141
+ }
142
+
143
+ try {
144
+ const { resolveInputSources } = require("../config/input-sources");
145
+ const sources = resolveInputSources(config, projectRoot);
146
+ if (!sources.length) throw Error();
147
+ for (const source of sources) {
148
+ const { diagnostics } = analyzeSchema(await fs.readFile(source.file, "utf8"), {
149
+ file: source.file,
150
+ isGdl: source.type === "gdl" || path.extname(source.file) === ".gdl",
151
+ config,
152
+ });
153
+ for (const d of diagnostics)
154
+ add(
155
+ "schema:" + d.code,
156
+ d.severity === "error" ? "fail" : "warning",
157
+ `${d.file}:${d.line}:${d.column}: ${d.message}`,
158
+ d.suggestion,
159
+ );
160
+ if (!diagnostics.length) {
161
+ add("schema", "pass", `Schema [${source.name}]: ${source.file}`);
162
+ }
163
+ }
164
+ } catch {
165
+ add(
166
+ "schema",
167
+ "fail",
168
+ "Configured schema cannot be read.",
169
+ "Correct input.file or input.sources[].file relative to the project root.",
170
+ );
171
+ }
172
+ if (config.fuse?.enabled)
173
+ add(
174
+ "starter",
175
+ (await fs.pathExists(config.fuse.starterPackPath)) ? "pass" : "fail",
176
+ "Fuse starter path checked.",
177
+ "Correct fuse.starterPackPath if the template directory is missing.",
178
+ );
179
+ if (!pkg)
180
+ add(
181
+ "application",
182
+ "warning",
183
+ "No generated package.json yet.",
184
+ "Run crudforge generate after validation passes.",
185
+ );
186
+ else {
187
+ const locks = [];
188
+ for (const name of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"])
189
+ if (await fs.pathExists(path.join(projectRoot, name))) locks.push(name);
190
+ const lockFor = {
191
+ npm: "package-lock.json",
192
+ pnpm: "pnpm-lock.yaml",
193
+ yarn: "yarn.lock",
194
+ };
195
+ const expected = pkg.packageManager?.split("@")[0] ||
196
+ Object.keys(lockFor).find((manager) => locks.length === 1 && lockFor[manager] === locks[0]) || "npm";
197
+ add(
198
+ "lockfile",
199
+ locks.length === 1 && locks[0] === lockFor[expected] ? "pass" : "fail",
200
+ `Package manager ${expected}; lockfiles: ${locks.join(", ") || "none"}.`,
201
+ "Keep the matching lockfile and use the declared package manager.",
202
+ );
203
+ if (locks.includes("package-lock.json"))
204
+ try {
205
+ const lock = await fs.readJson(
206
+ path.join(projectRoot, "package-lock.json"),
207
+ );
208
+ const root = lock.packages?.[""];
209
+ for (const section of ["dependencies", "devDependencies"])
210
+ if (
211
+ JSON.stringify(Object.entries(root?.[section] || {}).sort()) !==
212
+ JSON.stringify(Object.entries(pkg[section] || {}).sort())
213
+ )
214
+ add(
215
+ "lockfile-drift",
216
+ "fail",
217
+ `${section} differ between package.json and package-lock.json.`,
218
+ "Refresh the lockfile deliberately with npm install, then use npm ci.",
219
+ );
220
+ } catch {
221
+ add(
222
+ "lockfile",
223
+ "fail",
224
+ "package-lock.json is malformed.",
225
+ "Repair the lockfile.",
226
+ );
227
+ }
228
+ const pnp = await fs.pathExists(path.join(projectRoot, ".pnp.cjs"));
229
+ const missing = [];
230
+ for (const name of Object.keys({
231
+ ...pkg.dependencies,
232
+ ...pkg.devDependencies,
233
+ }))
234
+ if (
235
+ !(await fs.pathExists(
236
+ path.join(projectRoot, "node_modules", name, "package.json"),
237
+ ))
238
+ )
239
+ missing.push(name);
240
+ add(
241
+ "dependencies",
242
+ pnp ? "warning" : missing.length ? "fail" : "pass",
243
+ pnp
244
+ ? "Yarn PnP manifest exists; package resolution was not executed during read-only diagnosis."
245
+ : missing.length
246
+ ? `${missing.length} declared dependencies are missing.`
247
+ : "Declared dependency directories exist.",
248
+ "Run the project package manager install command; doctor never installs packages.",
249
+ );
250
+ }
251
+ let target = config.output?.directory || ".";
252
+ while (!(await fs.pathExists(target)) && path.dirname(target) !== target)
253
+ target = path.dirname(target);
254
+ try {
255
+ if (!(await fs.stat(target)).isDirectory()) throw Error();
256
+ await fs.access(target, fs.constants.W_OK);
257
+ add(
258
+ "output-path",
259
+ "pass",
260
+ "Output directory or its nearest parent is writable.",
261
+ );
262
+ } catch {
263
+ add(
264
+ "output-path",
265
+ "fail",
266
+ "Output directory cannot be created or written.",
267
+ "Correct output.directory or its directory permissions.",
268
+ );
269
+ }
270
+ const port = config.devServer?.port || 4200;
271
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
272
+ add(
273
+ "port",
274
+ "fail",
275
+ "devServer.port must be an integer from 1 to 65535.",
276
+ "Correct devServer.port.",
277
+ );
278
+ else {
279
+ const available = await portAvailable(port);
280
+ add(
281
+ "port",
282
+ available ? "pass" : "warning",
283
+ `Development port ${port} ${available ? "is available" : "is occupied or binding is not permitted"} on IPv4 loopback.`,
284
+ "If occupied, use another configured port or stop your own server manually.",
285
+ );
286
+ }
287
+ for (const [name, value] of Object.entries(config.api?.services || {})) {
288
+ for (const target of typeof value === "string"
289
+ ? [value]
290
+ : [value.dev, value.prod].filter(Boolean))
291
+ if (!/^https?:\/\//.test(target) || safeUrl(target) === "[invalid URL]")
292
+ add(
293
+ "api-target",
294
+ "fail",
295
+ `Service ${name} has an invalid target.`,
296
+ "Use an absolute HTTP(S) service URL.",
297
+ );
298
+ }
299
+ if (
300
+ config.api?.microservice &&
301
+ !config.api?.services?.[config.api.microservice] &&
302
+ !Object.keys(config.proxy?.routes || {}).length
303
+ )
304
+ add(
305
+ "api-routing",
306
+ "warning",
307
+ "No explicit proxy target for the configured microservice.",
308
+ "Verify same-origin deployment routing or configure api.services/proxy.routes.",
309
+ );
310
+ if (config.auth?.provider === "keycloak") {
311
+ const missing = ["realm", "clientId", "serverUrl"].filter(
312
+ (k) => !config.auth[k],
313
+ );
314
+ add(
315
+ "auth",
316
+ missing.length ? "fail" : "pass",
317
+ missing.length
318
+ ? `Missing Keycloak settings: ${missing.join(", ")}.`
319
+ : "Keycloak settings present; credentials and login are unverified.",
320
+ "Configure frontend authentication without embedding client secrets.",
321
+ );
322
+ }
323
+ for (const [route, value] of Object.entries(config.proxy?.routes || {})) {
324
+ const target = typeof value === "string" ? value : value.target;
325
+ if (target && !/^https?:\/\//.test(target))
326
+ add(
327
+ "proxy-target",
328
+ "fail",
329
+ `Proxy route ${route} needs an HTTP(S) target.`,
330
+ "Correct proxy.routes.",
331
+ );
332
+ }
333
+ if (
334
+ config.auth?.provider === "keycloak" &&
335
+ config.auth.serverUrl &&
336
+ !/^https?:\/\//.test(config.auth.serverUrl)
337
+ )
338
+ add(
339
+ "auth-url",
340
+ "fail",
341
+ "Keycloak server URL must be absolute HTTP(S).",
342
+ "Correct auth.serverUrl.",
343
+ );
344
+ const endpoints = config.doctor?.endpoints || [];
345
+ if (live) {
346
+ if (!endpoints.length)
347
+ add(
348
+ "live",
349
+ "warning",
350
+ "No documented read-only endpoints configured.",
351
+ "Set doctor.endpoints with explicit URLs and descriptions; no universal health endpoint is assumed.",
352
+ );
353
+ for (const endpoint of endpoints) {
354
+ const result = await probe(endpoint.url, config.doctor.timeoutMs);
355
+ add(
356
+ "live",
357
+ result.status,
358
+ `${safeUrl(endpoint.url)}: ${result.message}`,
359
+ endpoint.description,
360
+ );
361
+ }
362
+ } else
363
+ add(
364
+ "live",
365
+ "pass",
366
+ "Live requests disabled; local checks made no HTTP requests.",
367
+ );
368
+ const secrets = [];
369
+ function collect(value) {
370
+ if (value && typeof value === "object")
371
+ for (const [key, item] of Object.entries(value)) {
372
+ if (
373
+ /password|secret|token|credential|authorization/i.test(key) &&
374
+ typeof item === "string" &&
375
+ item.length
376
+ )
377
+ secrets.push(item);
378
+ else collect(item);
379
+ }
380
+ }
381
+ collect(config);
382
+ for (const check of checks)
383
+ for (const key of ["message", "next"])
384
+ for (const secret of secrets)
385
+ check[key] = check[key].split(secret).join("[REDACTED]");
386
+ return { ok: !checks.some((c) => c.status === "fail"), checks };
387
+ }
388
+ async function doctorCommand(options = {}) {
389
+ const result = await diagnose(options);
390
+ console.log(
391
+ options.json
392
+ ? JSON.stringify(result, null, 2)
393
+ : result.checks
394
+ .map(
395
+ (c) =>
396
+ `[${c.status.toUpperCase()}] ${c.message}${c.next ? "\n Next: " + c.next : ""}`,
397
+ )
398
+ .join("\n"),
399
+ );
400
+ process.exitCode = result.ok ? 0 : 1;
401
+ return result;
402
+ }
403
+ module.exports = { doctorCommand, diagnose, probe };