@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,127 @@
1
+ const assert = require('assert/strict');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const crypto = require('crypto');
6
+ const { spawnSync } = require('child_process');
7
+ const YAML = require('yaml');
8
+ const { extractPmbPreset } = require('../src/presets/pmb-test');
9
+ const { validateProxyRoutes } = require('../src/config/proxy-routes');
10
+ const cli = path.resolve(__dirname, '../cli.js');
11
+
12
+ function hash(directory) {
13
+ return fs.readdirSync(directory).sort().map(name => {
14
+ const file = path.join(directory, name);
15
+ return fs.statSync(file).isDirectory() ? [name, hash(file)] : [name, crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')];
16
+ });
17
+ }
18
+
19
+ async function test() {
20
+ const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'crudforge-preset-test-'));
21
+ const source = path.join(temp, 'source');
22
+ const app = path.join(temp, 'app');
23
+ const registry = path.join(temp, 'registry/crudforge/presets/pmb-test');
24
+ const env = { ...process.env, XDG_CONFIG_HOME: path.join(temp, 'registry'),
25
+ KEYCLOAK_ADMIN_USERNAME: 'ENV_USER_SENTINEL', KEYCLOAK_ADMIN_PASSWORD: 'ENV_PASSWORD_SENTINEL' };
26
+ const write = (file, content) => fs.outputFileSync(path.join(source, file), typeof content === 'string' ? content : JSON.stringify(content));
27
+ function run(cwd, args, success = true) {
28
+ const result = spawnSync(process.execPath, [cli, ...args], { cwd, env, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
29
+ assert.equal(result.status === 0, success, result.stdout + result.stderr);
30
+ return result.stdout + result.stderr;
31
+ }
32
+ try {
33
+ write('angular.json', { projects: { pmb: { architect: {
34
+ build: { configurations: { development: { fileReplacements: [{ replace: 'src/environments/environment.ts', with: 'src/environments/environment.development.ts' }] } } },
35
+ serve: { configurations: { development: { proxyConfig: 'proxy.dev.conf.json' } } },
36
+ } } } });
37
+ write('package.json', { scripts: { dev: 'ng serve --configuration development --proxy-config proxy.dev.conf.json --port=4217' } });
38
+ write('src/environments/environment.development.ts', `export const environment = {
39
+ useMockAuth: false, kcRealm: 'PMB-FIXTURE', kcClientId: 'public-client', kcClientSecret: '',
40
+ sso: '/realms/PMB-FIXTURE/protocol/openid-connect/token', kcUrl: '/realms/PMB-FIXTURE/protocol/openid-connect',
41
+ apiBaseUrl: '/', keycloakBaseUrl: '', sessionIdleTimeoutMs: 15 * 60 * 1000,
42
+ kcSpecialClientSecret: 'SECRET_SENTINEL', dadMockAuth: { password: 'PASSWORD_SENTINEL' },
43
+ neverExecute: (() => { throw new Error('source must not execute'); })(),
44
+ };`);
45
+ const proxy = {
46
+ '/realms': { target: 'http://localhost:30100', changeOrigin: true },
47
+ '/api/v1': { target: 'http://localhost:30100', changeOrigin: true, headers: { Authorization: 'TOKEN_SENTINEL' } },
48
+ '/pmb-referenceModule': { target: 'http://localhost:30101', secure: false, changeOrigin: false, ws: true,
49
+ pathRewrite: { '^/pmb-referenceModule': '/pmb-reference-module' } },
50
+ '/services/pmb-reference-module': { target: 'http://localhost:30101', pathRewrite: { '^/services/pmb-reference-module': '/pmb-reference-module' } },
51
+ '/services': { target: 'http://localhost:30102', pathRewrite: { '^/services': '' } },
52
+ };
53
+ write('proxy.dev.conf.json', proxy);
54
+ write('src/app/core/auth/auth.service.ts', `const SIGN_IN_OIDC_SCOPE = 'openid';
55
+ const ACCESS_TOKEN_EXPIRY_PROBE_INTERVAL_MS = 30 * 1000;
56
+ body.set('grant_type', 'password'); body.set('grant_type', 'refresh_token');
57
+ body.set('client_id', config.clientId); const logout = '/logout';`);
58
+ write('src/app/core/auth/auth.interceptor.ts', "headers.delete('X-Tenant-ID'); const bearer = `Bearer ${token}`;");
59
+ write('src/app/core/auth/access-control.service.ts', "readonly isUserAdmin = computed<boolean>(() => { return roles.some(role => role === 'PMB_ADMIN');\n});");
60
+ const entity = 'src/app/domains/admin/modules/apps/micro-functions/referenceModule/entities/province';
61
+ write(entity + '/province.model.ts', `export interface IProvince {
62
+ id?: number; index?: String | null; provinceCode?: String | null; provinceName?: String | null; isActive?: boolean | null;
63
+ createdBy?: String | null; createdDate?: dayjs.Dayjs | null; lastModifiedBy?: String | null; lastModifiedDate?: dayjs.Dayjs | null;
64
+ }`);
65
+ write(entity + '/service/province.service.ts', "protected resourceUrl = '/pmb-referenceModule/api/provinces';");
66
+ const sourceBefore = hash(source);
67
+ run(temp, ['init', app, '--preset', 'pmb-test', '--source', source]);
68
+ assert.deepEqual(hash(source), sourceBefore);
69
+ assert.deepEqual(fs.readdirSync(app).sort(), ['config', 'gdl']);
70
+ assert(fs.existsSync(path.join(registry, 'metadata.json')));
71
+ const input = fs.readFileSync(path.join(app, 'config/crudforge.config.yml'), 'utf8');
72
+ const config = YAML.parse(input);
73
+ const loaded = await require('../src/config/load-config').loadConfig(app, null, { projectPaths: true });
74
+ assert.deepEqual(loaded.config.auth.adminRoles, ['PMB_ADMIN']);
75
+ assert.equal(config.auth.realm, 'PMB-FIXTURE');
76
+ assert.equal(config.auth.session.idleTimeoutMs, 900000);
77
+ assert.equal(config.devServer.port, 4217);
78
+ assert.deepEqual(config.auth.adminRoles, ['PMB_ADMIN']);
79
+ assert.equal(config.auth.provision.enabled, false);
80
+ assert(!input.includes(source));
81
+ assert(!/SENTINEL/.test(input));
82
+ const beforeDuplicate = hash(registry);
83
+ run(temp, ['init', path.join(temp, 'duplicate'), '--preset', 'pmb-test', '--source', source], false);
84
+ assert.deepEqual(hash(registry), beforeDuplicate);
85
+ assert(!fs.existsSync(path.join(temp, 'duplicate')));
86
+
87
+ // Deleting the application and even removing the source does not lose the preset.
88
+ fs.removeSync(app);
89
+ fs.renameSync(source, source + '-offline');
90
+ run(temp, ['init', app, '--preset', 'pmb-test']);
91
+ assert.equal(fs.readFileSync(path.join(app, 'config/crudforge.config.yml'), 'utf8'), input);
92
+ run(app, ['generate']);
93
+ const generatedProxy = fs.readJsonSync(path.join(app, 'proxy.dev.conf.json'));
94
+ assert.equal(generatedProxy['/services/keycloak-admin'].pathRewrite['^/services/keycloak-admin'], '');
95
+ assert.equal(generatedProxy['/pmb-referenceModule'].changeOrigin, false);
96
+ assert.equal(generatedProxy['/pmb-referenceModule'].ws, true);
97
+ assert(Object.keys(generatedProxy).indexOf('/services/keycloak-admin') < Object.keys(generatedProxy).indexOf('/services'));
98
+ assert(!generatedProxy['/api/v1'].headers);
99
+ const service = fs.readFileSync(path.join(app, 'src/app/domains/admin/modules/apps/micro-functions/pmb-reference-module/entities/province/service/province.service.ts'), 'utf8');
100
+ assert(service.includes('/services/pmb-reference-module/api/provinces'));
101
+ const signIn = fs.readFileSync(path.join(app, 'src/app/domains/auth/features/sign-in/sign-in.ts'), 'utf8');
102
+ assert(!/SENTINEL/.test(signIn));
103
+ assert(signIn.includes("password: ''"));
104
+ assert.equal(fs.readJsonSync(path.join(app, 'angular.json')).projects.fuse.architect.serve.configurations.development.port, 4217);
105
+ assert.equal(fs.readFileSync(path.join(app, 'config/crudforge.config.yml'), 'utf8'), input);
106
+ assert(!fs.existsSync(path.join(app, 'src/app/core/user-management/user-management.routes.ts')));
107
+
108
+ fs.renameSync(source + '-offline', source);
109
+ proxy['/pmb-referenceModule'].target = 'http://localhost:30200';
110
+ write('proxy.dev.conf.json', proxy);
111
+ run(temp, ['preset', 'refresh', 'pmb-test', '--source', source]);
112
+ assert.equal(YAML.parse(fs.readFileSync(path.join(registry, 'config.yml'), 'utf8')).api.services['pmb-reference-module'].target, 'http://localhost:30200');
113
+ assert.equal(YAML.parse(fs.readFileSync(path.join(registry, 'config.yml'), 'utf8')).proxy.routes['/services/pmb-reference-module'].target, 'http://localhost:30200');
114
+ assert(fs.readdirSync(path.dirname(registry)).some(name => name.startsWith('pmb-test.backup-')));
115
+ assert.equal(fs.readFileSync(path.join(app, 'config/crudforge.config.yml'), 'utf8'), input);
116
+ const beforeFailedRefresh = hash(registry);
117
+ write('src/environments/environment.development.ts', 'export const environment = loadEnvironment();');
118
+ run(temp, ['preset', 'refresh', 'pmb-test', '--source', source], false);
119
+ assert.deepEqual(hash(registry), beforeFailedRefresh);
120
+ assert.throws(() => extractPmbPreset(source), /statically read/);
121
+ assert.throws(() => validateProxyRoutes({ '/api': { target: 'http://user:secret@localhost' } }), /without credentials/);
122
+ assert.throws(() => validateProxyRoutes({ '/api': { target: 'http://localhost', headers: {} } }), /unsupported option/);
123
+ console.log('PMB preset tests passed: extraction, source preservation, reuse, refresh/backup, secret exclusion, routing, and generation.');
124
+ } finally { await fs.remove(temp); }
125
+ }
126
+
127
+ test().catch(error => { console.error(error); process.exitCode = 1; });
@@ -0,0 +1,111 @@
1
+ const assert = require('assert/strict');
2
+ const fs = require('fs-extra');
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { spawnSync } = require('child_process');
7
+ const YAML = require('yaml');
8
+ const cli = path.resolve(__dirname, '../cli.js');
9
+
10
+ function run(cwd, args, succeeds = true) {
11
+ const result = spawnSync(process.execPath, [cli, ...args], { cwd, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
12
+ const output = result.stdout + result.stderr;
13
+ assert.equal(result.status === 0, succeeds, output);
14
+ return output;
15
+ }
16
+
17
+ function snapshot(dir) {
18
+ return Object.fromEntries(fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
19
+ const file = path.join(dir, entry.name);
20
+ if (entry.isDirectory()) return Object.entries(snapshot(file)).map(([key, value]) => [entry.name + '/' + key, value]);
21
+ return [[entry.name, crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')]];
22
+ }));
23
+ }
24
+
25
+ async function test() {
26
+ const sandbox = await fs.mkdtemp(path.join(os.tmpdir(), 'crudforge-workflow-test-'));
27
+ try {
28
+ const project = path.join(sandbox, 'project with spaces');
29
+ run(sandbox, ['init', project]);
30
+ assert.deepEqual(Object.keys(snapshot(project)).sort(), ['config/crudforge.config.yml', 'gdl/example.gdl']);
31
+ const configPath = path.join(project, 'config/crudforge.config.yml');
32
+ const schemaPath = path.join(project, 'gdl/example.gdl');
33
+ const config = YAML.parse(fs.readFileSync(configPath, 'utf8'));
34
+ assert.equal(config.input.file, './gdl/example.gdl');
35
+ assert.equal(config.output.directory, '.');
36
+ const original = snapshot(project);
37
+ run(sandbox, ['init', project], false);
38
+ assert.deepEqual(snapshot(project), original);
39
+ run(project, ['generate', '--dry-run']);
40
+ assert.deepEqual(snapshot(project), original);
41
+
42
+ const validConfig = fs.readFileSync(configPath, 'utf8');
43
+ for (const invalid of ['input: [', 'null', 'fuse: true', 'output:\n directory: 42',
44
+ 'output:\n overwritePolicy: invalid', 'input:\n file: missing.gdl']) {
45
+ fs.writeFileSync(configPath, invalid);
46
+ const before = snapshot(project);
47
+ const message = run(project, ['generate'], false);
48
+ assert(message.includes(configPath) || message.includes('missing.gdl'), message);
49
+ assert.deepEqual(snapshot(project), before);
50
+ }
51
+ fs.writeFileSync(configPath, validConfig);
52
+ const validGdl = fs.readFileSync(schemaPath, 'utf8');
53
+ fs.writeFileSync(schemaPath, 'entity Book {\n nonexistent title\n}\n');
54
+ const invalidSchema = snapshot(project);
55
+ assert(run(project, ['generate'], false).includes(schemaPath));
56
+ assert.deepEqual(snapshot(project), invalidSchema);
57
+ fs.writeFileSync(schemaPath, validGdl);
58
+ run(sandbox, ['generate', '--preferences', 'missing.yml'], false);
59
+
60
+ // Generated settings and inputs must remain portable after moving the project.
61
+ const moved = path.join(sandbox, 'relocated');
62
+ fs.renameSync(project, moved);
63
+ run(sandbox, ['generate', '--preferences', path.join(moved, 'config/crudforge.config.yml')]);
64
+ assert(fs.existsSync(path.join(moved, 'angular.json')));
65
+ assert(fs.existsSync(path.join(moved, 'package.json')));
66
+ assert(fs.existsSync(path.join(moved, 'src/app/domains/admin/modules/apps/micro-functions/book-service/entities/book/book.model.ts')));
67
+ assert.equal(fs.readFileSync(path.join(moved, 'config/crudforge.config.yml'), 'utf8'), validConfig);
68
+ assert.equal(fs.readFileSync(path.join(moved, 'gdl/example.gdl'), 'utf8'), validGdl);
69
+ assert.equal(YAML.parse(fs.readFileSync(path.join(moved, '.crudforge/config.yaml'), 'utf8')).input.file, 'gdl/example.gdl');
70
+
71
+ const packagePath = path.join(moved, 'package.json');
72
+ const customized = fs.readFileSync(packagePath, 'utf8') + '\n';
73
+ fs.writeFileSync(packagePath, customized);
74
+ const beforeConflict = snapshot(moved);
75
+ assert(run(moved, ['generate'], false).includes(packagePath));
76
+ assert.deepEqual(snapshot(moved), beforeConflict);
77
+ run(moved, ['generate', '--overwrite-policy', 'ask'], false);
78
+ assert.deepEqual(snapshot(moved), beforeConflict);
79
+ run(moved, ['generate', '--overwrite-policy', 'skip']);
80
+ assert.equal(fs.readFileSync(packagePath, 'utf8'), customized);
81
+ run(moved, ['generate', '--overwrite-policy', 'overwrite']);
82
+ assert.notEqual(fs.readFileSync(packagePath, 'utf8'), customized);
83
+ assert.equal(fs.readFileSync(path.join(moved, 'config/crudforge.config.yml'), 'utf8'), validConfig);
84
+ assert.equal(fs.readFileSync(path.join(moved, 'gdl/example.gdl'), 'utf8'), validGdl);
85
+
86
+ const model = path.join(moved, 'src/app/domains/admin/modules/apps/micro-functions/book-service/entities/book/book.model.ts');
87
+ fs.writeFileSync(model, fs.readFileSync(model, 'utf8').replace(
88
+ '// cforge-custom-code-start:model-additions',
89
+ '// cforge-custom-code-start:model-additions\nexport const custom = 123;'));
90
+ run(moved, ['generate', '--overwrite-policy', 'merge']);
91
+ assert(fs.readFileSync(model, 'utf8').includes('export const custom = 123;'));
92
+
93
+ const image = path.join(moved, 'public/images/logo/logo.png');
94
+ fs.writeFileSync(image, Buffer.from([0, 1, 2]));
95
+ const beforeBinaryConflict = snapshot(moved);
96
+ run(moved, ['generate', '--overwrite-policy', 'merge'], false);
97
+ assert.deepEqual(snapshot(moved), beforeBinaryConflict);
98
+
99
+ const legacy = path.join(sandbox, 'legacy');
100
+ fs.ensureDirSync(legacy);
101
+ run(legacy, ['init-config']);
102
+ run(legacy, ['validate', 'example.gdl']);
103
+ run(legacy, ['generate', 'example.gdl', 'output', '--microservice', 'book-service']);
104
+ assert(fs.existsSync(path.join(legacy, 'output/entities/book/book.model.ts')));
105
+ console.log('Project workflow passed: init, edit, relocate, validate, generate, dry-run, conflict policies, and legacy CLI.');
106
+ } finally {
107
+ await fs.remove(sandbox);
108
+ }
109
+ }
110
+
111
+ test().catch(error => { console.error(error); process.exitCode = 1; });
@@ -0,0 +1,250 @@
1
+ const assert = require("node:assert/strict"),
2
+ fs = require("fs-extra"),
3
+ os = require("os"),
4
+ path = require("path"),
5
+ http = require("http");
6
+ const { analyzeSchema } = require("../src/validator/diagnostics");
7
+ const { diagnose, probe } = require("../src/commands/doctor");
8
+ const { generateFromSource } = require("../src");
9
+ const { generateProject } = require("../src/generator/project-output");
10
+ const { spawnSync } = require("child_process");
11
+ (async () => {
12
+ const root = await fs.mkdtemp(
13
+ path.join(os.tmpdir(), "crudforge-quality-regression-"),
14
+ );
15
+ try {
16
+ const invalid = [
17
+ ["entity A {\n string a\n string a\n}", "field-collision"],
18
+ ["entity A {\n thing a\n}", "unknown-type"],
19
+ ["entity A {\n string a min(3)\n}", "invalid-rule"],
20
+ [
21
+ "entity A {\n string a\n}\nrelationship ManyToOne {\n A{b} to Missing\n}",
22
+ "missing-entity",
23
+ ],
24
+ ["@SoftDelete entity A {\n string a\n}", "unsupported-annotation"],
25
+ ["entity A-B {\n string a\n}", "invalid-name"],
26
+ [
27
+ "entity FooBar {\n string a\n}\nentity Foo_Bar {\n string a\n}",
28
+ "output-collision",
29
+ ],
30
+ [
31
+ "enum State { X, X }\nentity A {\n State state\n}",
32
+ "duplicate-enum-value",
33
+ ],
34
+ [
35
+ "entity A {\n long bId\n}\nentity B {\n string name\n}\nrelationship ManyToOne {\n A{b} to B\n}",
36
+ "relationship-collision",
37
+ ],
38
+ ["entity A {\n string a", "syntax"],
39
+ ];
40
+ for (const [source, code] of invalid) {
41
+ const result = analyzeSchema(source, { isGdl: true, file: "input.gdl" });
42
+ assert.ok(
43
+ result.diagnostics.some((d) => d.code === code),
44
+ JSON.stringify(result),
45
+ );
46
+ assert.ok(
47
+ result.diagnostics.every(
48
+ (d) => d.file && d.line && d.column && d.suggestion,
49
+ ),
50
+ );
51
+ }
52
+ const valid =
53
+ "enum State { ACTIVE, INACTIVE }\nentity Item {\n string title required\n State state\n int quantity min(-2) max(10)\n}";
54
+ for (const kind of ["go-duck", "jhipster", "custom"]) {
55
+ const result = analyzeSchema(valid, {
56
+ isGdl: true,
57
+ config: { backend: { kind } },
58
+ });
59
+ assert.equal(
60
+ result.diagnostics.filter((d) => d.severity === "error").length,
61
+ 0,
62
+ );
63
+ assert.ok(
64
+ result.diagnostics.some((d) => d.code === "backend-unverified"),
65
+ );
66
+ }
67
+ const input = path.join(root, "input.gdl");
68
+ await fs.writeFile(input, invalid[0][0]);
69
+ const cli = spawnSync(
70
+ process.execPath,
71
+ [path.resolve("cli.js"), "validate", input, "--json"],
72
+ { encoding: "utf8", cwd: root },
73
+ );
74
+ assert.equal(cli.status, 1);
75
+ assert.equal(JSON.parse(cli.stdout).ok, false);
76
+ const configPath = path.join(root, "crudforge.config.yml");
77
+ await fs.writeFile(
78
+ configPath,
79
+ "input:\n file: input.gdl\n type: gdl\nfuse:\n enabled: true\n cleanLayout: true\n includePrebuilt: false\ngenerate:\n globals:\n tests: true\ntesting:\n ci: true\napi:\n microservice: example\n",
80
+ );
81
+ const output = path.join(root, "app");
82
+ const generateJson = spawnSync(
83
+ process.execPath,
84
+ [
85
+ path.resolve("cli.js"),
86
+ "generate",
87
+ input,
88
+ output,
89
+ "--preferences",
90
+ configPath,
91
+ "--json",
92
+ "--yes",
93
+ ],
94
+ { encoding: "utf8", cwd: root },
95
+ );
96
+ assert.equal(generateJson.status, 1);
97
+ assert.equal(JSON.parse(generateJson.stdout).ok, false);
98
+ const patternSchema =
99
+ "entity Item {\n string code required pattern(^[A-Z]{2}[0-9]{2}$) // code label\n}";
100
+ const patternResult = analyzeSchema(patternSchema, { isGdl: true });
101
+ assert.equal(
102
+ patternResult.parsed.entities[0].fields[0].fieldValidateRules[1],
103
+ "pattern(^[A-Z]{2}[0-9]{2}$)",
104
+ );
105
+ assert.equal(
106
+ patternResult.parsed.entities[0].fields[0].comment,
107
+ "code label",
108
+ );
109
+
110
+ await assert.rejects(
111
+ generateFromSource(invalid[0][0], {
112
+ fileType: "gdl",
113
+ configPath,
114
+ outputDirectory: output,
115
+ }),
116
+ /field-collision/,
117
+ );
118
+ assert.equal(await fs.pathExists(output), false);
119
+ await fs.writeFile(input, valid);
120
+ const result = await generateFromSource(valid, {
121
+ fileType: "gdl",
122
+ configPath,
123
+ outputDirectory: output,
124
+ });
125
+ for (const file of [
126
+ "tests/generated/smoke.spec.cjs",
127
+ "tests/generated/mock.cjs",
128
+ ".github/workflows/crudforge-smoke.yml",
129
+ ])
130
+ assert.ok(await fs.pathExists(path.join(output, file)));
131
+ assert.equal(await fs.pathExists(path.join(output, "tests/custom")), false);
132
+ const custom = path.join(output, "tests/custom/my.spec.cjs");
133
+ await fs.outputFile(custom, "// developer-owned");
134
+ const managed = path.join(output, "tests/generated/smoke.spec.cjs");
135
+ await fs.appendFile(managed, "\n// changed by developer");
136
+ result.config.output.overwritePolicy = "fail";
137
+ await assert.rejects(
138
+ generateProject(result.parsed, result.config),
139
+ /conflict|Existing/i,
140
+ );
141
+ assert.equal(await fs.readFile(custom, "utf8"), "// developer-owned");
142
+ assert.ok(
143
+ (await fs.readFile(managed, "utf8")).includes("changed by developer"),
144
+ );
145
+ const mock = await fs.readFile(
146
+ path.join(output, "tests/generated/mock.cjs"),
147
+ "utf8",
148
+ );
149
+ assert.match(mock, /route\.abort\(['"]blockedbyclient['"]\)/);
150
+ const pw = await fs.readFile(
151
+ path.join(output, "tests/generated/playwright.config.cjs"),
152
+ "utf8",
153
+ );
154
+ assert.match(pw, /reuseExistingServer:\s*false/);
155
+ assert.match(pw, /serviceWorkers:\s*['"]block['"]/);
156
+ const ciCode = await fs.readFile(
157
+ path.join(output, "tests/generated/ci.cjs"),
158
+ "utf8",
159
+ );
160
+ for (const [manager, version, flag] of [
161
+ ["npm", "10", "ci"],
162
+ ["pnpm", "10", "--frozen-lockfile"],
163
+ ["yarn", "1.22.0", "--frozen-lockfile"],
164
+ ["yarn", "4.0.0", "--immutable"],
165
+ ]) {
166
+ let called;
167
+ const locks = {
168
+ npm: "package-lock.json",
169
+ pnpm: "pnpm-lock.yaml",
170
+ yarn: "yarn.lock",
171
+ };
172
+ require("vm").runInNewContext(ciCode, {
173
+ process: { argv: ["node", "ci.cjs", "install"], platform: "linux" },
174
+ require: (name) =>
175
+ name === "fs"
176
+ ? { existsSync: (file) => file === locks[manager] }
177
+ : name === "child_process"
178
+ ? {
179
+ execFileSync: (cmd, args) => {
180
+ called = { cmd, args };
181
+ },
182
+ }
183
+ : { packageManager: manager + "@" + version },
184
+ });
185
+ assert.equal(called.cmd, manager);
186
+ assert.ok(called.args.includes(flag));
187
+ }
188
+ const workflow = require("yaml").parse(
189
+ await fs.readFile(
190
+ path.join(output, ".github/workflows/crudforge-smoke.yml"),
191
+ "utf8",
192
+ ),
193
+ );
194
+ assert.ok(
195
+ workflow.jobs.smoke.steps.some(
196
+ (step) => step.run === "node tests/generated/ci.cjs build",
197
+ ),
198
+ );
199
+ assert.deepEqual(
200
+ (await fs.readJson(path.join(output, "tsconfig.smoke.json"))).files,
201
+ ["src/main.ts"],
202
+ );
203
+ const diagnosed = await diagnose({ cwd: root, nodeVersion: "18.0.0" });
204
+ assert.ok(
205
+ diagnosed.checks.some((c) => c.id === "node" && c.status === "fail"),
206
+ );
207
+ assert.ok(
208
+ diagnosed.checks.some((c) => c.id === "live" && c.status === "pass"),
209
+ );
210
+ await fs.writeFile(configPath, "password: secret-do-not-print\ninput: [");
211
+ const malformed = await diagnose({ cwd: root });
212
+ assert.equal(malformed.ok, false);
213
+ assert.ok(!JSON.stringify(malformed).includes("secret-do-not-print"));
214
+ const server = http.createServer((req, res) => {
215
+ if (req.url === "/timeout") return;
216
+ res.writeHead(Number(req.url.slice(1)) || 200);
217
+ res.end();
218
+ });
219
+ await new Promise((r) => server.listen(0, "127.0.0.1", r));
220
+ const base = "http://127.0.0.1:" + server.address().port;
221
+ try {
222
+ for (const status of [200, 401, 403, 500]) {
223
+ const result = await probe(base + "/" + status, 500);
224
+ assert.equal(
225
+ result.status,
226
+ status === 200 ? "pass" : status < 500 ? "warning" : "fail",
227
+ );
228
+ }
229
+ const start = Date.now();
230
+ const timed = await probe(base + "/timeout", 100);
231
+ assert.match(timed.message, /timed out/);
232
+ assert.ok(Date.now() - start < 1500);
233
+ assert.equal(
234
+ (await probe("http://user:password@localhost/health")).status,
235
+ "fail",
236
+ );
237
+ } finally {
238
+ server.closeAllConnections?.();
239
+ await new Promise((r) => server.close(r));
240
+ }
241
+ console.log(
242
+ "Quality feature regressions passed: diagnostics, CLI JSON, fail-fast output, backend labels, generated tests/CI, custom preservation, local doctor and bounded live probes.",
243
+ );
244
+ } finally {
245
+ await fs.remove(root);
246
+ }
247
+ })().catch((e) => {
248
+ console.error(e);
249
+ process.exitCode = 1;
250
+ });
@@ -0,0 +1,83 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ async function verifyCommands() {
6
+ console.log('๐Ÿงช Starting CRUDForge CLI Commands Verification Tests...');
7
+
8
+ const cwd = process.cwd();
9
+ const cliPath = path.join(__dirname, '..', 'cli.js');
10
+ const tmpDir = path.join(__dirname, 'tmp-cli');
11
+
12
+ // Clean up
13
+ await fs.remove(tmpDir);
14
+ await fs.ensureDir(tmpDir);
15
+
16
+ try {
17
+ console.log('๐Ÿงช Testing "crud-forge init-config"...');
18
+ execSync(`node ${cliPath} init-config`, { cwd: tmpDir, stdio: 'inherit' });
19
+
20
+ // Verify files created
21
+ const configExists = await fs.pathExists(path.join(tmpDir, 'crudforge.config.yml'));
22
+ const jdlExists = await fs.pathExists(path.join(tmpDir, 'example.jdl'));
23
+ const gdlExists = await fs.pathExists(path.join(tmpDir, 'example.gdl'));
24
+
25
+ if (!configExists || !jdlExists || !gdlExists) {
26
+ throw new Error('โŒ init-config failed: Did not create all expected files.');
27
+ }
28
+ console.log('โœ… init-config generated files successfully.');
29
+
30
+ console.log('๐Ÿงช Testing "crud-forge validate <file>"...');
31
+ execSync(`node ${cliPath} validate example.jdl`, { cwd: tmpDir, stdio: 'inherit' });
32
+ console.log('โœ… validate passed on valid JDL.');
33
+
34
+ console.log('๐Ÿงช Testing Granular Configuration Entity-Only Generation...');
35
+ // We modify the generated config to turn off models and list components
36
+ const configPath = path.join(tmpDir, 'crudforge.config.yml');
37
+ let configStr = await fs.readFile(configPath, 'utf8');
38
+
39
+ // Disable models and list components
40
+ configStr = configStr.replace('models: true', 'models: false');
41
+ configStr = configStr.replace('list: true', 'list: false');
42
+
43
+ // Ensure fuse is disabled
44
+ configStr = configStr.replace('enabled: true', 'enabled: false');
45
+
46
+ await fs.writeFile(configPath, configStr);
47
+
48
+ const outDir = path.join(tmpDir, 'output');
49
+
50
+ // Run generation
51
+ execSync(`node ${cliPath} example.jdl ${outDir} -m test-service`, { cwd: tmpDir, stdio: 'inherit' });
52
+
53
+ // Verify models were skipped
54
+ const bookModelExists = await fs.pathExists(path.join(outDir, 'entities', 'book', 'book.model.ts'));
55
+ if (bookModelExists) {
56
+ throw new Error('โŒ Granular configuration failed: Generated book.model.ts even though config.generate.models is false.');
57
+ }
58
+
59
+ // Verify list component was skipped
60
+ const bookListExists = await fs.pathExists(path.join(outDir, 'entities', 'book', 'components', 'presentation', 'list', 'book-list.component.ts'));
61
+ if (bookListExists) {
62
+ throw new Error('โŒ Granular configuration failed: Generated book-list.component.ts even though config.generate.components.list is false.');
63
+ }
64
+
65
+ // Verify services still generated
66
+ const bookServiceExists = await fs.pathExists(path.join(outDir, 'entities', 'book', 'service', 'book.service.ts'));
67
+ if (!bookServiceExists) {
68
+ throw new Error('โŒ Granular configuration failed: Missing book.service.ts, which should have generated.');
69
+ }
70
+
71
+ console.log('โœ… Granular entity-only generation behaves correctly (skips excluded files).');
72
+
73
+ console.log('๐ŸŽ‰ SUCCESS: All Command Verification Tests Passed!');
74
+ } finally {
75
+ // Cleanup tmp dir
76
+ await fs.remove(tmpDir);
77
+ }
78
+ }
79
+
80
+ verifyCommands().catch(err => {
81
+ console.error(err.message || err);
82
+ process.exit(1);
83
+ });