@carlonicora/nextjs-jsonapi 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/dist/{BlockNoteEditor-6NVBI7XL.js → BlockNoteEditor-CNXQ3WL3.js} +7 -7
  2. package/dist/{BlockNoteEditor-6NVBI7XL.js.map → BlockNoteEditor-CNXQ3WL3.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-4VOBTXWC.mjs → BlockNoteEditor-DC33K4IF.mjs} +3 -3
  4. package/dist/{chunk-KPRHIJO6.js → chunk-KDFDGXCX.js} +53 -53
  5. package/dist/{chunk-KPRHIJO6.js.map → chunk-KDFDGXCX.js.map} +1 -1
  6. package/dist/{chunk-FYRFMABS.js → chunk-L6LH7WA4.js} +21 -2
  7. package/dist/chunk-L6LH7WA4.js.map +1 -0
  8. package/dist/{chunk-MA2L2PL2.mjs → chunk-MHVXFWZY.mjs} +20 -1
  9. package/dist/chunk-MHVXFWZY.mjs.map +1 -0
  10. package/dist/{chunk-HP4Q5ZVG.mjs → chunk-Y4XI3AZR.mjs} +3 -3
  11. package/dist/client/index.js +3 -3
  12. package/dist/client/index.mjs +2 -2
  13. package/dist/components/index.js +3 -3
  14. package/dist/components/index.mjs +2 -2
  15. package/dist/contexts/index.js +3 -3
  16. package/dist/contexts/index.mjs +2 -2
  17. package/dist/features/index.d.mts +3 -1
  18. package/dist/features/index.d.ts +3 -1
  19. package/dist/features/index.js +4 -2
  20. package/dist/features/index.js.map +1 -1
  21. package/dist/features/index.mjs +3 -1
  22. package/dist/hooks/index.js +3 -3
  23. package/dist/hooks/index.mjs +2 -2
  24. package/dist/scripts/generate-web-module/generator.d.ts +15 -0
  25. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -0
  26. package/dist/scripts/generate-web-module/generator.js +320 -0
  27. package/dist/scripts/generate-web-module/generator.js.map +1 -0
  28. package/dist/scripts/generate-web-module/index.d.ts +16 -0
  29. package/dist/scripts/generate-web-module/index.d.ts.map +1 -0
  30. package/dist/scripts/generate-web-module/index.js +80 -0
  31. package/dist/scripts/generate-web-module/index.js.map +1 -0
  32. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +14 -0
  33. package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -0
  34. package/dist/scripts/generate-web-module/templates/components/container.template.js +124 -0
  35. package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -0
  36. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +15 -0
  37. package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -0
  38. package/dist/scripts/generate-web-module/templates/components/content.template.js +39 -0
  39. package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -0
  40. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts +14 -0
  41. package/dist/scripts/generate-web-module/templates/components/deleter.template.d.ts.map +1 -0
  42. package/dist/scripts/generate-web-module/templates/components/deleter.template.js +51 -0
  43. package/dist/scripts/generate-web-module/templates/components/deleter.template.js.map +1 -0
  44. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +14 -0
  45. package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +1 -0
  46. package/dist/scripts/generate-web-module/templates/components/details.template.js +109 -0
  47. package/dist/scripts/generate-web-module/templates/components/details.template.js.map +1 -0
  48. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +14 -0
  49. package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts.map +1 -0
  50. package/dist/scripts/generate-web-module/templates/components/editor.template.js +459 -0
  51. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -0
  52. package/dist/scripts/generate-web-module/templates/components/index.d.ts +15 -0
  53. package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -0
  54. package/dist/scripts/generate-web-module/templates/components/index.js +29 -0
  55. package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -0
  56. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts +14 -0
  57. package/dist/scripts/generate-web-module/templates/components/list-container.template.d.ts.map +1 -0
  58. package/dist/scripts/generate-web-module/templates/components/list-container.template.js +30 -0
  59. package/dist/scripts/generate-web-module/templates/components/list-container.template.js.map +1 -0
  60. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts +14 -0
  61. package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -0
  62. package/dist/scripts/generate-web-module/templates/components/list.template.js +84 -0
  63. package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -0
  64. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts +14 -0
  65. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -0
  66. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +184 -0
  67. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -0
  68. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +14 -0
  69. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -0
  70. package/dist/scripts/generate-web-module/templates/components/selector.template.js +199 -0
  71. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -0
  72. package/dist/scripts/generate-web-module/templates/context.template.d.ts +14 -0
  73. package/dist/scripts/generate-web-module/templates/context.template.d.ts.map +1 -0
  74. package/dist/scripts/generate-web-module/templates/context.template.js +121 -0
  75. package/dist/scripts/generate-web-module/templates/context.template.js.map +1 -0
  76. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts +14 -0
  77. package/dist/scripts/generate-web-module/templates/data/fields.template.d.ts.map +1 -0
  78. package/dist/scripts/generate-web-module/templates/data/fields.template.js +55 -0
  79. package/dist/scripts/generate-web-module/templates/data/fields.template.js.map +1 -0
  80. package/dist/scripts/generate-web-module/templates/data/index.d.ts +10 -0
  81. package/dist/scripts/generate-web-module/templates/data/index.d.ts.map +1 -0
  82. package/dist/scripts/generate-web-module/templates/data/index.js +26 -0
  83. package/dist/scripts/generate-web-module/templates/data/index.js.map +1 -0
  84. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts +14 -0
  85. package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -0
  86. package/dist/scripts/generate-web-module/templates/data/interface.template.js +116 -0
  87. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -0
  88. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts +14 -0
  89. package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -0
  90. package/dist/scripts/generate-web-module/templates/data/model.template.js +274 -0
  91. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -0
  92. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts +14 -0
  93. package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -0
  94. package/dist/scripts/generate-web-module/templates/data/service.template.js +168 -0
  95. package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -0
  96. package/dist/scripts/generate-web-module/templates/index.d.ts +12 -0
  97. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -0
  98. package/dist/scripts/generate-web-module/templates/index.js +37 -0
  99. package/dist/scripts/generate-web-module/templates/index.js.map +1 -0
  100. package/dist/scripts/generate-web-module/templates/module.template.d.ts +14 -0
  101. package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -0
  102. package/dist/scripts/generate-web-module/templates/module.template.js +64 -0
  103. package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -0
  104. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts +14 -0
  105. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.d.ts.map +1 -0
  106. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js +65 -0
  107. package/dist/scripts/generate-web-module/templates/pages/detail-page.template.js.map +1 -0
  108. package/dist/scripts/generate-web-module/templates/pages/index.d.ts +8 -0
  109. package/dist/scripts/generate-web-module/templates/pages/index.d.ts.map +1 -0
  110. package/dist/scripts/generate-web-module/templates/pages/index.js +13 -0
  111. package/dist/scripts/generate-web-module/templates/pages/index.js.map +1 -0
  112. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts +14 -0
  113. package/dist/scripts/generate-web-module/templates/pages/list-page.template.d.ts.map +1 -0
  114. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js +37 -0
  115. package/dist/scripts/generate-web-module/templates/pages/list-page.template.js.map +1 -0
  116. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts +14 -0
  117. package/dist/scripts/generate-web-module/templates/table-hook.template.d.ts.map +1 -0
  118. package/dist/scripts/generate-web-module/templates/table-hook.template.js +174 -0
  119. package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -0
  120. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts +55 -0
  121. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -0
  122. package/dist/scripts/generate-web-module/transformers/field-mapper.js +179 -0
  123. package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -0
  124. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts +78 -0
  125. package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -0
  126. package/dist/scripts/generate-web-module/transformers/i18n-generator.js +182 -0
  127. package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -0
  128. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +106 -0
  129. package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -0
  130. package/dist/scripts/generate-web-module/transformers/import-resolver.js +193 -0
  131. package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -0
  132. package/dist/scripts/generate-web-module/transformers/index.d.ts +12 -0
  133. package/dist/scripts/generate-web-module/transformers/index.d.ts.map +1 -0
  134. package/dist/scripts/generate-web-module/transformers/index.js +28 -0
  135. package/dist/scripts/generate-web-module/transformers/index.js.map +1 -0
  136. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts +60 -0
  137. package/dist/scripts/generate-web-module/transformers/name-transformer.d.ts.map +1 -0
  138. package/dist/scripts/generate-web-module/transformers/name-transformer.js +115 -0
  139. package/dist/scripts/generate-web-module/transformers/name-transformer.js.map +1 -0
  140. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +57 -0
  141. package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -0
  142. package/dist/scripts/generate-web-module/transformers/parent-detector.js +88 -0
  143. package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -0
  144. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +68 -0
  145. package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -0
  146. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +219 -0
  147. package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -0
  148. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +68 -0
  149. package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -0
  150. package/dist/scripts/generate-web-module/types/field-mapping.types.js +129 -0
  151. package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -0
  152. package/dist/scripts/generate-web-module/types/index.d.ts +9 -0
  153. package/dist/scripts/generate-web-module/types/index.d.ts.map +1 -0
  154. package/dist/scripts/generate-web-module/types/index.js +25 -0
  155. package/dist/scripts/generate-web-module/types/index.js.map +1 -0
  156. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +40 -0
  157. package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -0
  158. package/dist/scripts/generate-web-module/types/json-schema.interface.js +10 -0
  159. package/dist/scripts/generate-web-module/types/json-schema.interface.js.map +1 -0
  160. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +128 -0
  161. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -0
  162. package/dist/scripts/generate-web-module/types/template-data.interface.js +9 -0
  163. package/dist/scripts/generate-web-module/types/template-data.interface.js.map +1 -0
  164. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts +29 -0
  165. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.d.ts.map +1 -0
  166. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js +153 -0
  167. package/dist/scripts/generate-web-module/utils/bootstrapper-updater.js.map +1 -0
  168. package/dist/scripts/generate-web-module/utils/file-writer.d.ts +38 -0
  169. package/dist/scripts/generate-web-module/utils/file-writer.d.ts.map +1 -0
  170. package/dist/scripts/generate-web-module/utils/file-writer.js +126 -0
  171. package/dist/scripts/generate-web-module/utils/file-writer.js.map +1 -0
  172. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts +28 -0
  173. package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -0
  174. package/dist/scripts/generate-web-module/utils/i18n-updater.js +122 -0
  175. package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -0
  176. package/dist/scripts/generate-web-module/utils/index.d.ts +9 -0
  177. package/dist/scripts/generate-web-module/utils/index.d.ts.map +1 -0
  178. package/dist/scripts/generate-web-module/utils/index.js +20 -0
  179. package/dist/scripts/generate-web-module/utils/index.js.map +1 -0
  180. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts +46 -0
  181. package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -0
  182. package/dist/scripts/generate-web-module/validators/json-schema-validator.js +265 -0
  183. package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -0
  184. package/package.json +27 -21
  185. package/scripts/generate-web-module/generator.ts +363 -0
  186. package/scripts/generate-web-module/index.ts +49 -0
  187. package/scripts/generate-web-module/templates/components/container.template.ts +129 -0
  188. package/scripts/generate-web-module/templates/components/content.template.ts +40 -0
  189. package/scripts/generate-web-module/templates/components/deleter.template.ts +51 -0
  190. package/scripts/generate-web-module/templates/components/details.template.ts +115 -0
  191. package/scripts/generate-web-module/templates/components/editor.template.ts +495 -0
  192. package/scripts/generate-web-module/templates/components/index.ts +18 -0
  193. package/scripts/generate-web-module/templates/components/list-container.template.ts +30 -0
  194. package/scripts/generate-web-module/templates/components/list.template.ts +91 -0
  195. package/scripts/generate-web-module/templates/components/multi-selector.template.ts +184 -0
  196. package/scripts/generate-web-module/templates/components/selector.template.ts +199 -0
  197. package/scripts/generate-web-module/templates/context.template.ts +121 -0
  198. package/scripts/generate-web-module/templates/data/fields.template.ts +64 -0
  199. package/scripts/generate-web-module/templates/data/index.ts +10 -0
  200. package/scripts/generate-web-module/templates/data/interface.template.ts +136 -0
  201. package/scripts/generate-web-module/templates/data/model.template.ts +327 -0
  202. package/scripts/generate-web-module/templates/data/service.template.ts +185 -0
  203. package/scripts/generate-web-module/templates/index.ts +34 -0
  204. package/scripts/generate-web-module/templates/module.template.ts +69 -0
  205. package/scripts/generate-web-module/templates/pages/detail-page.template.ts +65 -0
  206. package/scripts/generate-web-module/templates/pages/index.ts +8 -0
  207. package/scripts/generate-web-module/templates/pages/list-page.template.ts +37 -0
  208. package/scripts/generate-web-module/templates/table-hook.template.ts +182 -0
  209. package/scripts/generate-web-module/transformers/field-mapper.ts +201 -0
  210. package/scripts/generate-web-module/transformers/i18n-generator.ts +199 -0
  211. package/scripts/generate-web-module/transformers/import-resolver.ts +250 -0
  212. package/scripts/generate-web-module/transformers/index.ts +12 -0
  213. package/scripts/generate-web-module/transformers/name-transformer.ts +115 -0
  214. package/scripts/generate-web-module/transformers/parent-detector.ts +87 -0
  215. package/scripts/generate-web-module/transformers/relationship-resolver.ts +221 -0
  216. package/scripts/generate-web-module/tsconfig.json +24 -0
  217. package/scripts/generate-web-module/types/field-mapping.types.ts +141 -0
  218. package/scripts/generate-web-module/types/index.ts +9 -0
  219. package/scripts/generate-web-module/types/json-schema.interface.ts +42 -0
  220. package/scripts/generate-web-module/types/template-data.interface.ts +164 -0
  221. package/scripts/generate-web-module/utils/bootstrapper-updater.ts +145 -0
  222. package/scripts/generate-web-module/utils/file-writer.ts +115 -0
  223. package/scripts/generate-web-module/utils/i18n-updater.ts +108 -0
  224. package/scripts/generate-web-module/utils/index.ts +9 -0
  225. package/scripts/generate-web-module/validators/json-schema-validator.ts +306 -0
  226. package/src/features/user/index.ts +1 -1
  227. package/dist/chunk-FYRFMABS.js.map +0 -1
  228. package/dist/chunk-MA2L2PL2.mjs.map +0 -1
  229. /package/dist/{BlockNoteEditor-4VOBTXWC.mjs.map → BlockNoteEditor-DC33K4IF.mjs.map} +0 -0
  230. /package/dist/{chunk-HP4Q5ZVG.mjs.map → chunk-Y4XI3AZR.mjs.map} +0 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Bootstrapper Updater
3
+ *
4
+ * Updates the Bootstrapper.ts file to register new modules.
5
+ */
6
+
7
+ import * as fs from "fs";
8
+ import { FrontendTemplateData } from "../types/template-data.interface";
9
+
10
+ const BOOTSTRAPPER_PATH = "apps/web/src/config/Bootstrapper.ts";
11
+
12
+ export interface BootstrapperUpdateResult {
13
+ success: boolean;
14
+ message: string;
15
+ alreadyRegistered?: boolean;
16
+ }
17
+
18
+ /**
19
+ * Update Bootstrapper.ts to add a new module
20
+ *
21
+ * @param data - Frontend template data
22
+ * @param webBasePath - Base path to web app
23
+ * @param dryRun - Whether to perform a dry run
24
+ * @returns Update result
25
+ */
26
+ export function updateBootstrapper(
27
+ data: FrontendTemplateData,
28
+ webBasePath: string,
29
+ dryRun: boolean = false
30
+ ): BootstrapperUpdateResult {
31
+ const { names, targetDir } = data;
32
+ const bootstrapperPath = `${webBasePath}/${BOOTSTRAPPER_PATH}`;
33
+
34
+ // Check if file exists
35
+ if (!fs.existsSync(bootstrapperPath)) {
36
+ return {
37
+ success: false,
38
+ message: `Bootstrapper.ts not found at ${bootstrapperPath}`,
39
+ };
40
+ }
41
+
42
+ let content = fs.readFileSync(bootstrapperPath, "utf-8");
43
+
44
+ // Check if module is already registered
45
+ const modulePattern = new RegExp(`${names.pascalCase}Module`, "g");
46
+ if (modulePattern.test(content)) {
47
+ return {
48
+ success: true,
49
+ message: `Module ${names.pascalCase} already registered in Bootstrapper.ts`,
50
+ alreadyRegistered: true,
51
+ };
52
+ }
53
+
54
+ // Add import statement
55
+ const importStatement = `import { ${names.pascalCase}Module } from "@/features/${targetDir}/${names.kebabCase}/${names.pascalCase}Module";`;
56
+
57
+ // Find the right place to insert import (after "// Feature module imports" or at end of imports)
58
+ const featureImportsMarker = "// Feature module imports";
59
+ const lastImportMatch = content.match(/^import .* from ".*";$/gm);
60
+
61
+ if (content.includes(featureImportsMarker)) {
62
+ // Find the last import after the marker
63
+ const markerIndex = content.indexOf(featureImportsMarker);
64
+ const afterMarker = content.substring(markerIndex);
65
+ const nextNewlineIndex = afterMarker.indexOf("\n");
66
+ const insertPosition = markerIndex + nextNewlineIndex;
67
+
68
+ // Find next empty line or start of const
69
+ const afterMarkerContent = content.substring(insertPosition);
70
+ const constIndex = afterMarkerContent.indexOf("\nconst ");
71
+
72
+ if (constIndex > 0) {
73
+ const insertAt = insertPosition + constIndex;
74
+ content = content.slice(0, insertAt) + `\n${importStatement}` + content.slice(insertAt);
75
+ }
76
+ } else if (lastImportMatch) {
77
+ // Insert after last import
78
+ const lastImport = lastImportMatch[lastImportMatch.length - 1];
79
+ const lastImportIndex = content.lastIndexOf(lastImport);
80
+ const insertAt = lastImportIndex + lastImport.length;
81
+ content = content.slice(0, insertAt) + `\n${importStatement}` + content.slice(insertAt);
82
+ }
83
+
84
+ // Add module to allModules object
85
+ const allModulesPattern = /const allModules = \{[^}]+\}/s;
86
+ const allModulesMatch = content.match(allModulesPattern);
87
+
88
+ if (allModulesMatch) {
89
+ const allModulesBlock = allModulesMatch[0];
90
+ // Find the last line before closing brace
91
+ const lines = allModulesBlock.split("\n");
92
+ const lastEntryIndex = lines.length - 1;
93
+
94
+ // Add new module entry before the closing brace
95
+ const newEntry = ` ${names.pascalCase}: ${names.pascalCase}Module(moduleFactory),`;
96
+
97
+ // Find if there's a trailing comma on the last entry
98
+ for (let i = lastEntryIndex - 1; i >= 0; i--) {
99
+ const line = lines[i].trim();
100
+ if (line && !line.startsWith("//")) {
101
+ // Add comma if needed
102
+ if (!line.endsWith(",")) {
103
+ lines[i] = lines[i].replace(/(\s*)$/, ",$1");
104
+ }
105
+ break;
106
+ }
107
+ }
108
+
109
+ // Insert new entry
110
+ lines.splice(lastEntryIndex, 0, newEntry);
111
+ const updatedBlock = lines.join("\n");
112
+ content = content.replace(allModulesBlock, updatedBlock);
113
+ }
114
+
115
+ if (dryRun) {
116
+ return {
117
+ success: true,
118
+ message: `[DRY RUN] Would update Bootstrapper.ts with ${names.pascalCase}Module`,
119
+ };
120
+ }
121
+
122
+ // Write updated content
123
+ fs.writeFileSync(bootstrapperPath, content, "utf-8");
124
+
125
+ return {
126
+ success: true,
127
+ message: `Updated Bootstrapper.ts with ${names.pascalCase}Module`,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Generate the import statement for a module
133
+ */
134
+ export function generateImportStatement(data: FrontendTemplateData): string {
135
+ const { names, targetDir } = data;
136
+ return `import { ${names.pascalCase}Module } from "@/features/${targetDir}/${names.kebabCase}/${names.pascalCase}Module";`;
137
+ }
138
+
139
+ /**
140
+ * Generate the module registration entry
141
+ */
142
+ export function generateModuleEntry(data: FrontendTemplateData): string {
143
+ const { names } = data;
144
+ return ` ${names.pascalCase}: ${names.pascalCase}Module(moduleFactory),`;
145
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * File Writer Utility
3
+ *
4
+ * Handles file writing with conflict detection and directory creation.
5
+ */
6
+
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { GeneratedFile } from "../types/template-data.interface";
10
+
11
+ export interface WriteOptions {
12
+ dryRun?: boolean;
13
+ force?: boolean;
14
+ }
15
+
16
+ export interface WriteResult {
17
+ path: string;
18
+ status: "created" | "updated" | "skipped" | "dry-run";
19
+ existed: boolean;
20
+ }
21
+
22
+ /**
23
+ * Write a single file to disk
24
+ *
25
+ * @param file - Generated file data
26
+ * @param options - Write options
27
+ * @returns Write result
28
+ */
29
+ export function writeFile(file: GeneratedFile, options: WriteOptions = {}): WriteResult {
30
+ const { dryRun = false, force = false } = options;
31
+ const existed = fs.existsSync(file.path);
32
+
33
+ if (dryRun) {
34
+ return {
35
+ path: file.path,
36
+ status: "dry-run",
37
+ existed,
38
+ };
39
+ }
40
+
41
+ // Check if file exists and we're not forcing
42
+ if (existed && !force) {
43
+ return {
44
+ path: file.path,
45
+ status: "skipped",
46
+ existed: true,
47
+ };
48
+ }
49
+
50
+ // Create directory if needed
51
+ const dir = path.dirname(file.path);
52
+ if (!fs.existsSync(dir)) {
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ }
55
+
56
+ // Write the file
57
+ fs.writeFileSync(file.path, file.content, "utf-8");
58
+
59
+ return {
60
+ path: file.path,
61
+ status: existed ? "updated" : "created",
62
+ existed,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Write multiple files to disk
68
+ *
69
+ * @param files - Array of generated files
70
+ * @param options - Write options
71
+ * @returns Array of write results
72
+ */
73
+ export function writeFiles(files: GeneratedFile[], options: WriteOptions = {}): WriteResult[] {
74
+ return files.map((file) => writeFile(file, options));
75
+ }
76
+
77
+ /**
78
+ * Print write results to console
79
+ *
80
+ * @param results - Array of write results
81
+ */
82
+ export function printResults(results: WriteResult[]): void {
83
+ const grouped = {
84
+ created: results.filter((r) => r.status === "created"),
85
+ updated: results.filter((r) => r.status === "updated"),
86
+ skipped: results.filter((r) => r.status === "skipped"),
87
+ dryRun: results.filter((r) => r.status === "dry-run"),
88
+ };
89
+
90
+ if (grouped.dryRun.length > 0) {
91
+ console.log("\n📋 DRY RUN - Files that would be created:");
92
+ grouped.dryRun.forEach((r) => {
93
+ console.log(` ${r.existed ? "🔄" : "✨"} ${r.path}`);
94
+ });
95
+ }
96
+
97
+ if (grouped.created.length > 0) {
98
+ console.log("\n✨ Created files:");
99
+ grouped.created.forEach((r) => console.log(` ${r.path}`));
100
+ }
101
+
102
+ if (grouped.updated.length > 0) {
103
+ console.log("\n🔄 Updated files:");
104
+ grouped.updated.forEach((r) => console.log(` ${r.path}`));
105
+ }
106
+
107
+ if (grouped.skipped.length > 0) {
108
+ console.log("\n⏭️ Skipped (already exist, use --force to overwrite):");
109
+ grouped.skipped.forEach((r) => console.log(` ${r.path}`));
110
+ }
111
+
112
+ console.log(
113
+ `\nTotal: ${results.length} files (${grouped.created.length} created, ${grouped.updated.length} updated, ${grouped.skipped.length} skipped)`
114
+ );
115
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * i18n Updater
3
+ *
4
+ * Updates the messages/en.json file with new module translations.
5
+ */
6
+
7
+ import * as fs from "fs";
8
+ import { FrontendTemplateData } from "../types/template-data.interface";
9
+ import { buildI18nMessages } from "../transformers/i18n-generator";
10
+
11
+ const MESSAGES_PATH = "apps/web/messages/en.json";
12
+
13
+ export interface I18nUpdateResult {
14
+ success: boolean;
15
+ message: string;
16
+ alreadyExists?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Update messages/en.json with new module translations
21
+ *
22
+ * @param data - Frontend template data
23
+ * @param webBasePath - Base path to web app
24
+ * @param dryRun - Whether to perform a dry run
25
+ * @returns Update result
26
+ */
27
+ export function updateI18n(
28
+ data: FrontendTemplateData,
29
+ webBasePath: string,
30
+ dryRun: boolean = false
31
+ ): I18nUpdateResult {
32
+ const { names, i18nKeys } = data;
33
+ const messagesPath = `${webBasePath}/${MESSAGES_PATH}`;
34
+
35
+ // Check if file exists
36
+ if (!fs.existsSync(messagesPath)) {
37
+ return {
38
+ success: false,
39
+ message: `Messages file not found at ${messagesPath}`,
40
+ };
41
+ }
42
+
43
+ const content = fs.readFileSync(messagesPath, "utf-8");
44
+ let messages: Record<string, any>;
45
+
46
+ try {
47
+ messages = JSON.parse(content);
48
+ } catch (e) {
49
+ return {
50
+ success: false,
51
+ message: `Failed to parse messages/en.json: ${e}`,
52
+ };
53
+ }
54
+
55
+ // Check if module already exists in features
56
+ if (messages.features && messages.features[names.camelCase]) {
57
+ return {
58
+ success: true,
59
+ message: `Module ${names.camelCase} already exists in messages/en.json`,
60
+ alreadyExists: true,
61
+ };
62
+ }
63
+
64
+ // Build the i18n messages for this module
65
+ const moduleMessages = buildI18nMessages(i18nKeys);
66
+
67
+ // Add to features section
68
+ if (!messages.features) {
69
+ messages.features = {};
70
+ }
71
+ messages.features[names.camelCase] = moduleMessages.features[i18nKeys.moduleName];
72
+
73
+ // Add to types section (if not exists)
74
+ if (!messages.types) {
75
+ messages.types = {};
76
+ }
77
+ const typesKey = Object.keys(moduleMessages.types)[0];
78
+ if (typesKey && !messages.types[names.pluralCamel]) {
79
+ messages.types[names.pluralCamel] = moduleMessages.types[typesKey];
80
+ }
81
+
82
+ if (dryRun) {
83
+ return {
84
+ success: true,
85
+ message: `[DRY RUN] Would update messages/en.json with ${names.camelCase} translations`,
86
+ };
87
+ }
88
+
89
+ // Write updated content with proper formatting
90
+ const updatedContent = JSON.stringify(messages, null, 2);
91
+ fs.writeFileSync(messagesPath, updatedContent, "utf-8");
92
+
93
+ return {
94
+ success: true,
95
+ message: `Updated messages/en.json with ${names.camelCase} translations`,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Preview the i18n messages that would be added
101
+ *
102
+ * @param data - Frontend template data
103
+ * @returns Preview of messages
104
+ */
105
+ export function previewI18nMessages(data: FrontendTemplateData): string {
106
+ const moduleMessages = buildI18nMessages(data.i18nKeys);
107
+ return JSON.stringify(moduleMessages, null, 2);
108
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Utils Index
3
+ *
4
+ * Exports all utility functions.
5
+ */
6
+
7
+ export { writeFile, writeFiles, printResults, type WriteOptions, type WriteResult } from "./file-writer";
8
+ export { updateBootstrapper, generateImportStatement, generateModuleEntry, type BootstrapperUpdateResult } from "./bootstrapper-updater";
9
+ export { updateI18n, previewI18nMessages, type I18nUpdateResult } from "./i18n-updater";
@@ -0,0 +1,306 @@
1
+ /**
2
+ * JSON Schema Validator
3
+ *
4
+ * Validates JSON module definition files for the frontend generator.
5
+ * Note: Backend-specific fields (toNode, relationshipName) are warnings, not errors.
6
+ */
7
+
8
+ import { JsonModuleDefinition } from "../types/json-schema.interface";
9
+
10
+ export interface ValidationError {
11
+ field: string;
12
+ message: string;
13
+ severity: "error" | "warning";
14
+ }
15
+
16
+ /**
17
+ * Validate JSON module definition for frontend generation
18
+ *
19
+ * @param schema - JSON module definition
20
+ * @returns Array of validation errors (empty if valid)
21
+ */
22
+ export function validateJsonSchema(schema: any): ValidationError[] {
23
+ const errors: ValidationError[] = [];
24
+
25
+ // Required fields
26
+ const required = ["moduleName", "endpointName", "targetDir"];
27
+ for (const field of required) {
28
+ if (!schema[field]) {
29
+ errors.push({
30
+ field,
31
+ message: `${field} is required`,
32
+ severity: "error",
33
+ });
34
+ }
35
+ }
36
+
37
+ // Module name format (PascalCase)
38
+ if (schema.moduleName && !/^[A-Z][a-zA-Z0-9]*$/.test(schema.moduleName)) {
39
+ errors.push({
40
+ field: "moduleName",
41
+ message: 'Must be PascalCase (e.g., "Comment", "Discussion")',
42
+ severity: "error",
43
+ });
44
+ }
45
+
46
+ // Endpoint format (kebab-case plural)
47
+ if (schema.endpointName && !/^[a-z][a-z0-9-]*$/.test(schema.endpointName)) {
48
+ errors.push({
49
+ field: "endpointName",
50
+ message: 'Must be kebab-case (e.g., "comments", "discussions")',
51
+ severity: "error",
52
+ });
53
+ }
54
+
55
+ // Target directory validation
56
+ if (schema.targetDir && !["features", "foundations"].includes(schema.targetDir)) {
57
+ errors.push({
58
+ field: "targetDir",
59
+ message: 'Must be either "features" or "foundations"',
60
+ severity: "error",
61
+ });
62
+ }
63
+
64
+ // Module ID validation (required for frontend)
65
+ if (!schema.moduleId) {
66
+ errors.push({
67
+ field: "moduleId",
68
+ message: "moduleId is required for frontend module registration",
69
+ severity: "error",
70
+ });
71
+ } else if (!/^[a-f0-9-]{36}$/i.test(schema.moduleId)) {
72
+ errors.push({
73
+ field: "moduleId",
74
+ message: "moduleId must be a valid UUID",
75
+ severity: "warning",
76
+ });
77
+ }
78
+
79
+ // Fields validation
80
+ if (schema.fields && Array.isArray(schema.fields)) {
81
+ schema.fields.forEach((field: any, index: number) => {
82
+ if (!field.name) {
83
+ errors.push({
84
+ field: `fields[${index}].name`,
85
+ message: "Field name is required",
86
+ severity: "error",
87
+ });
88
+ }
89
+
90
+ if (!field.type) {
91
+ errors.push({
92
+ field: `fields[${index}].type`,
93
+ message: "Field type is required",
94
+ severity: "error",
95
+ });
96
+ }
97
+
98
+ if (field.nullable === undefined) {
99
+ errors.push({
100
+ field: `fields[${index}].nullable`,
101
+ message: "Field nullable flag is required",
102
+ severity: "error",
103
+ });
104
+ }
105
+
106
+ // Validate known types
107
+ const validTypes = ["string", "number", "boolean", "date", "any"];
108
+ if (field.type && !validTypes.includes(field.type)) {
109
+ errors.push({
110
+ field: `fields[${index}].type`,
111
+ message: `Unknown type "${field.type}". Valid types: ${validTypes.join(", ")}`,
112
+ severity: "warning",
113
+ });
114
+ }
115
+ });
116
+ }
117
+
118
+ // Relationships validation
119
+ if (schema.relationships && Array.isArray(schema.relationships)) {
120
+ schema.relationships.forEach((rel: any, index: number) => {
121
+ if (!rel.name) {
122
+ errors.push({
123
+ field: `relationships[${index}].name`,
124
+ message: "Relationship name is required",
125
+ severity: "error",
126
+ });
127
+ }
128
+
129
+ if (!rel.directory) {
130
+ errors.push({
131
+ field: `relationships[${index}].directory`,
132
+ message: "Relationship directory is required",
133
+ severity: "error",
134
+ });
135
+ }
136
+
137
+ // Directory should be features or foundations
138
+ if (rel.directory && !["features", "foundations"].includes(rel.directory)) {
139
+ errors.push({
140
+ field: `relationships[${index}].directory`,
141
+ message: 'Relationship directory should be "features" or "foundations"',
142
+ severity: "warning",
143
+ });
144
+ }
145
+
146
+ if (rel.single === undefined) {
147
+ errors.push({
148
+ field: `relationships[${index}].single`,
149
+ message: "Relationship single flag is required",
150
+ severity: "error",
151
+ });
152
+ }
153
+
154
+ if (rel.nullable === undefined) {
155
+ errors.push({
156
+ field: `relationships[${index}].nullable`,
157
+ message: "Relationship nullable flag is required",
158
+ severity: "error",
159
+ });
160
+ }
161
+
162
+ // Backend-specific fields are warnings only (not used in frontend)
163
+ if (!rel.relationshipName) {
164
+ errors.push({
165
+ field: `relationships[${index}].relationshipName`,
166
+ message: "relationshipName missing (backend-specific, not used in frontend)",
167
+ severity: "warning",
168
+ });
169
+ }
170
+
171
+ if (rel.toNode === undefined) {
172
+ errors.push({
173
+ field: `relationships[${index}].toNode`,
174
+ message: "toNode missing (backend-specific, not used in frontend)",
175
+ severity: "warning",
176
+ });
177
+ }
178
+ });
179
+ }
180
+
181
+ return errors;
182
+ }
183
+
184
+ /**
185
+ * Format validation errors for display
186
+ *
187
+ * @param errors - Validation errors
188
+ * @returns Formatted error message
189
+ */
190
+ export function formatValidationErrors(errors: ValidationError[]): string {
191
+ if (errors.length === 0) {
192
+ return "No validation errors";
193
+ }
194
+
195
+ const errorCount = errors.filter((e) => e.severity === "error").length;
196
+ const warningCount = errors.filter((e) => e.severity === "warning").length;
197
+
198
+ const lines: string[] = [];
199
+
200
+ if (errorCount > 0) {
201
+ lines.push(`\n❌ ${errorCount} error(s):`);
202
+ errors
203
+ .filter((e) => e.severity === "error")
204
+ .forEach((error) => {
205
+ lines.push(` • ${error.field}: ${error.message}`);
206
+ });
207
+ }
208
+
209
+ if (warningCount > 0) {
210
+ lines.push(`\n⚠️ ${warningCount} warning(s):`);
211
+ errors
212
+ .filter((e) => e.severity === "warning")
213
+ .forEach((error) => {
214
+ lines.push(` • ${error.field}: ${error.message}`);
215
+ });
216
+ }
217
+
218
+ return lines.join("\n");
219
+ }
220
+
221
+ /**
222
+ * Validation result interface
223
+ */
224
+ export interface ValidationResult {
225
+ data: JsonModuleDefinition | null;
226
+ errors: string[];
227
+ warnings: string[];
228
+ }
229
+
230
+ /**
231
+ * Validate and parse JSON file from path
232
+ *
233
+ * @param filePath - Path to JSON file
234
+ * @returns Validation result with data, errors, and warnings
235
+ */
236
+ export function parseAndValidate(filePath: string): ValidationResult {
237
+ const result: ValidationResult = {
238
+ data: null,
239
+ errors: [],
240
+ warnings: [],
241
+ };
242
+
243
+ // Read file
244
+ let content: string;
245
+ try {
246
+ const fs = require("fs");
247
+ if (!fs.existsSync(filePath)) {
248
+ result.errors.push(`File not found: ${filePath}`);
249
+ return result;
250
+ }
251
+ content = fs.readFileSync(filePath, "utf-8");
252
+ } catch (e) {
253
+ result.errors.push(`Failed to read file: ${(e as Error).message}`);
254
+ return result;
255
+ }
256
+
257
+ // Parse JSON
258
+ let schema: any;
259
+ try {
260
+ schema = JSON.parse(content);
261
+ } catch (e) {
262
+ result.errors.push(`Invalid JSON: ${(e as Error).message}`);
263
+ return result;
264
+ }
265
+
266
+ // Handle array format (for bulk import compatibility)
267
+ if (Array.isArray(schema)) {
268
+ if (schema.length === 0) {
269
+ result.errors.push("JSON array is empty");
270
+ return result;
271
+ }
272
+ if (schema.length > 1) {
273
+ result.warnings.push(
274
+ `JSON file contains ${schema.length} definitions. Only processing the first one.`
275
+ );
276
+ }
277
+ schema = schema[0];
278
+ }
279
+
280
+ // Validate
281
+ const validationErrors = validateJsonSchema(schema);
282
+
283
+ // Separate errors and warnings
284
+ validationErrors.forEach((ve) => {
285
+ const msg = `${ve.field}: ${ve.message}`;
286
+ if (ve.severity === "error") {
287
+ result.errors.push(msg);
288
+ } else {
289
+ result.warnings.push(msg);
290
+ }
291
+ });
292
+
293
+ // Only set data if validation passed
294
+ if (result.errors.length === 0) {
295
+ result.data = schema as JsonModuleDefinition;
296
+ }
297
+
298
+ return result;
299
+ }
300
+
301
+ /**
302
+ * Check if validation result passed (no errors)
303
+ */
304
+ export function validationPassed(result: ValidationResult): boolean {
305
+ return result.errors.length === 0 && result.data !== null;
306
+ }
@@ -1,3 +1,3 @@
1
1
  export * from "./author.module";
2
- // Note: contexts are exported from @carlonicora/nextjs-jsonapi/contexts to avoid "use client" issues
3
2
  export * from "./data";
3
+ export * from "./user.module";