d365fo-mcp 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 (1055) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/bridge/bridgeAdapter.d.ts +319 -0
  4. package/dist/bridge/bridgeAdapter.d.ts.map +1 -0
  5. package/dist/bridge/bridgeAdapter.js +2003 -0
  6. package/dist/bridge/bridgeAdapter.js.map +1 -0
  7. package/dist/bridge/bridgeClient.d.ts +255 -0
  8. package/dist/bridge/bridgeClient.d.ts.map +1 -0
  9. package/dist/bridge/bridgeClient.js +741 -0
  10. package/dist/bridge/bridgeClient.js.map +1 -0
  11. package/dist/bridge/bridgeTypes.d.ts +561 -0
  12. package/dist/bridge/bridgeTypes.d.ts.map +1 -0
  13. package/dist/bridge/bridgeTypes.js +6 -0
  14. package/dist/bridge/bridgeTypes.js.map +1 -0
  15. package/dist/bridge/debouncedRefresh.d.ts +16 -0
  16. package/dist/bridge/debouncedRefresh.d.ts.map +1 -0
  17. package/dist/bridge/debouncedRefresh.js +77 -0
  18. package/dist/bridge/debouncedRefresh.js.map +1 -0
  19. package/dist/bridge/index.d.ts +21 -0
  20. package/dist/bridge/index.d.ts.map +1 -0
  21. package/dist/bridge/index.js +20 -0
  22. package/dist/bridge/index.js.map +1 -0
  23. package/dist/cli/commands/config.d.ts +8 -0
  24. package/dist/cli/commands/config.d.ts.map +1 -0
  25. package/dist/cli/commands/config.js +77 -0
  26. package/dist/cli/commands/config.js.map +1 -0
  27. package/dist/cli/commands/doctor.d.ts +2 -0
  28. package/dist/cli/commands/doctor.d.ts.map +1 -0
  29. package/dist/cli/commands/doctor.js +177 -0
  30. package/dist/cli/commands/doctor.js.map +1 -0
  31. package/dist/cli/commands/indexCmd.d.ts +8 -0
  32. package/dist/cli/commands/indexCmd.d.ts.map +1 -0
  33. package/dist/cli/commands/indexCmd.js +78 -0
  34. package/dist/cli/commands/indexCmd.js.map +1 -0
  35. package/dist/cli/commands/instance.d.ts +4 -0
  36. package/dist/cli/commands/instance.d.ts.map +1 -0
  37. package/dist/cli/commands/instance.js +183 -0
  38. package/dist/cli/commands/instance.js.map +1 -0
  39. package/dist/cli/commands/setup.d.ts +4 -0
  40. package/dist/cli/commands/setup.d.ts.map +1 -0
  41. package/dist/cli/commands/setup.js +246 -0
  42. package/dist/cli/commands/setup.js.map +1 -0
  43. package/dist/cli/commands/start.d.ts +2 -0
  44. package/dist/cli/commands/start.d.ts.map +1 -0
  45. package/dist/cli/commands/start.js +49 -0
  46. package/dist/cli/commands/start.js.map +1 -0
  47. package/dist/cli/commands/update.d.ts +4 -0
  48. package/dist/cli/commands/update.d.ts.map +1 -0
  49. package/dist/cli/commands/update.js +55 -0
  50. package/dist/cli/commands/update.js.map +1 -0
  51. package/dist/cli/context.d.ts +26 -0
  52. package/dist/cli/context.d.ts.map +1 -0
  53. package/dist/cli/context.js +38 -0
  54. package/dist/cli/context.js.map +1 -0
  55. package/dist/cli/envFile.d.ts +23 -0
  56. package/dist/cli/envFile.d.ts.map +1 -0
  57. package/dist/cli/envFile.js +79 -0
  58. package/dist/cli/envFile.js.map +1 -0
  59. package/dist/cli/exec.d.ts +13 -0
  60. package/dist/cli/exec.d.ts.map +1 -0
  61. package/dist/cli/exec.js +47 -0
  62. package/dist/cli/exec.js.map +1 -0
  63. package/dist/cli/index.d.ts +3 -0
  64. package/dist/cli/index.d.ts.map +1 -0
  65. package/dist/cli/index.js +122 -0
  66. package/dist/cli/index.js.map +1 -0
  67. package/dist/cli/instances.d.ts +19 -0
  68. package/dist/cli/instances.d.ts.map +1 -0
  69. package/dist/cli/instances.js +60 -0
  70. package/dist/cli/instances.js.map +1 -0
  71. package/dist/cli/mcpJson.d.ts +15 -0
  72. package/dist/cli/mcpJson.d.ts.map +1 -0
  73. package/dist/cli/mcpJson.js +44 -0
  74. package/dist/cli/mcpJson.js.map +1 -0
  75. package/dist/cli/settingsPrompt.d.ts +20 -0
  76. package/dist/cli/settingsPrompt.d.ts.map +1 -0
  77. package/dist/cli/settingsPrompt.js +150 -0
  78. package/dist/cli/settingsPrompt.js.map +1 -0
  79. package/dist/cli/settingsStore.d.ts +47 -0
  80. package/dist/cli/settingsStore.d.ts.map +1 -0
  81. package/dist/cli/settingsStore.js +144 -0
  82. package/dist/cli/settingsStore.js.map +1 -0
  83. package/dist/cli/target.d.ts +23 -0
  84. package/dist/cli/target.d.ts.map +1 -0
  85. package/dist/cli/target.js +62 -0
  86. package/dist/cli/target.js.map +1 -0
  87. package/dist/cli/ui.d.ts +26 -0
  88. package/dist/cli/ui.d.ts.map +1 -0
  89. package/dist/cli/ui.js +48 -0
  90. package/dist/cli/ui.js.map +1 -0
  91. package/dist/cli/xppConfig.d.ts +35 -0
  92. package/dist/cli/xppConfig.d.ts.map +1 -0
  93. package/dist/cli/xppConfig.js +97 -0
  94. package/dist/cli/xppConfig.js.map +1 -0
  95. package/dist/config/configFile.d.ts +50 -0
  96. package/dist/config/configFile.d.ts.map +1 -0
  97. package/dist/config/configFile.js +152 -0
  98. package/dist/config/configFile.js.map +1 -0
  99. package/dist/config/settings.d.ts +58 -0
  100. package/dist/config/settings.d.ts.map +1 -0
  101. package/dist/config/settings.js +641 -0
  102. package/dist/config/settings.js.map +1 -0
  103. package/dist/database/download.d.ts +27 -0
  104. package/dist/database/download.d.ts.map +1 -0
  105. package/dist/database/download.js +241 -0
  106. package/dist/database/download.js.map +1 -0
  107. package/dist/eval/audit/knowledgeAudit.d.ts +66 -0
  108. package/dist/eval/audit/knowledgeAudit.d.ts.map +1 -0
  109. package/dist/eval/audit/knowledgeAudit.js +117 -0
  110. package/dist/eval/audit/knowledgeAudit.js.map +1 -0
  111. package/dist/eval/audit/knowledgeAuditCli.d.ts +17 -0
  112. package/dist/eval/audit/knowledgeAuditCli.d.ts.map +1 -0
  113. package/dist/eval/audit/knowledgeAuditCli.js +83 -0
  114. package/dist/eval/audit/knowledgeAuditCli.js.map +1 -0
  115. package/dist/eval/audit/knowledgeRefs.d.ts +37 -0
  116. package/dist/eval/audit/knowledgeRefs.d.ts.map +1 -0
  117. package/dist/eval/audit/knowledgeRefs.js +150 -0
  118. package/dist/eval/audit/knowledgeRefs.js.map +1 -0
  119. package/dist/eval/audit/symbolLookup.d.ts +23 -0
  120. package/dist/eval/audit/symbolLookup.d.ts.map +1 -0
  121. package/dist/eval/audit/symbolLookup.js +80 -0
  122. package/dist/eval/audit/symbolLookup.js.map +1 -0
  123. package/dist/eval/coverage/coverage.d.ts +65 -0
  124. package/dist/eval/coverage/coverage.d.ts.map +1 -0
  125. package/dist/eval/coverage/coverage.js +136 -0
  126. package/dist/eval/coverage/coverage.js.map +1 -0
  127. package/dist/eval/coverage/coverageCli.d.ts +13 -0
  128. package/dist/eval/coverage/coverageCli.d.ts.map +1 -0
  129. package/dist/eval/coverage/coverageCli.js +106 -0
  130. package/dist/eval/coverage/coverageCli.js.map +1 -0
  131. package/dist/eval/coverage/sources.d.ts +17 -0
  132. package/dist/eval/coverage/sources.d.ts.map +1 -0
  133. package/dist/eval/coverage/sources.js +40 -0
  134. package/dist/eval/coverage/sources.js.map +1 -0
  135. package/dist/eval/coverage/taxonomy.d.ts +59 -0
  136. package/dist/eval/coverage/taxonomy.d.ts.map +1 -0
  137. package/dist/eval/coverage/taxonomy.js +400 -0
  138. package/dist/eval/coverage/taxonomy.js.map +1 -0
  139. package/dist/eval/improver/caseMining.d.ts +55 -0
  140. package/dist/eval/improver/caseMining.d.ts.map +1 -0
  141. package/dist/eval/improver/caseMining.js +64 -0
  142. package/dist/eval/improver/caseMining.js.map +1 -0
  143. package/dist/eval/improver/caseMiningCli.d.ts +10 -0
  144. package/dist/eval/improver/caseMiningCli.d.ts.map +1 -0
  145. package/dist/eval/improver/caseMiningCli.js +50 -0
  146. package/dist/eval/improver/caseMiningCli.js.map +1 -0
  147. package/dist/eval/improver/cli.d.ts +11 -0
  148. package/dist/eval/improver/cli.d.ts.map +1 -0
  149. package/dist/eval/improver/cli.js +73 -0
  150. package/dist/eval/improver/cli.js.map +1 -0
  151. package/dist/eval/improver/cluster.d.ts +47 -0
  152. package/dist/eval/improver/cluster.d.ts.map +1 -0
  153. package/dist/eval/improver/cluster.js +0 -0
  154. package/dist/eval/improver/cluster.js.map +1 -0
  155. package/dist/eval/improver/corpusIO.d.ts +26 -0
  156. package/dist/eval/improver/corpusIO.d.ts.map +1 -0
  157. package/dist/eval/improver/corpusIO.js +50 -0
  158. package/dist/eval/improver/corpusIO.js.map +1 -0
  159. package/dist/eval/improver/fixBrief.d.ts +34 -0
  160. package/dist/eval/improver/fixBrief.d.ts.map +1 -0
  161. package/dist/eval/improver/fixBrief.js +69 -0
  162. package/dist/eval/improver/fixBrief.js.map +1 -0
  163. package/dist/eval/improver/fixBriefCli.d.ts +11 -0
  164. package/dist/eval/improver/fixBriefCli.d.ts.map +1 -0
  165. package/dist/eval/improver/fixBriefCli.js +46 -0
  166. package/dist/eval/improver/fixBriefCli.js.map +1 -0
  167. package/dist/eval/improver/flakeDetection.d.ts +50 -0
  168. package/dist/eval/improver/flakeDetection.d.ts.map +1 -0
  169. package/dist/eval/improver/flakeDetection.js +0 -0
  170. package/dist/eval/improver/flakeDetection.js.map +1 -0
  171. package/dist/eval/improver/flakeDetectionCli.d.ts +8 -0
  172. package/dist/eval/improver/flakeDetectionCli.d.ts.map +1 -0
  173. package/dist/eval/improver/flakeDetectionCli.js +27 -0
  174. package/dist/eval/improver/flakeDetectionCli.js.map +1 -0
  175. package/dist/eval/improver/heldout.d.ts +44 -0
  176. package/dist/eval/improver/heldout.d.ts.map +1 -0
  177. package/dist/eval/improver/heldout.js +52 -0
  178. package/dist/eval/improver/heldout.js.map +1 -0
  179. package/dist/eval/improver/knowledgeFeedback.d.ts +56 -0
  180. package/dist/eval/improver/knowledgeFeedback.d.ts.map +1 -0
  181. package/dist/eval/improver/knowledgeFeedback.js +88 -0
  182. package/dist/eval/improver/knowledgeFeedback.js.map +1 -0
  183. package/dist/eval/improver/knowledgeFeedbackCli.d.ts +8 -0
  184. package/dist/eval/improver/knowledgeFeedbackCli.d.ts.map +1 -0
  185. package/dist/eval/improver/knowledgeFeedbackCli.js +28 -0
  186. package/dist/eval/improver/knowledgeFeedbackCli.js.map +1 -0
  187. package/dist/eval/improver/report.d.ts +36 -0
  188. package/dist/eval/improver/report.d.ts.map +1 -0
  189. package/dist/eval/improver/report.js +61 -0
  190. package/dist/eval/improver/report.js.map +1 -0
  191. package/dist/eval/improver/reportCli.d.ts +7 -0
  192. package/dist/eval/improver/reportCli.d.ts.map +1 -0
  193. package/dist/eval/improver/reportCli.js +27 -0
  194. package/dist/eval/improver/reportCli.js.map +1 -0
  195. package/dist/eval/oracle/actualArtifactResolution.d.ts +49 -0
  196. package/dist/eval/oracle/actualArtifactResolution.d.ts.map +1 -0
  197. package/dist/eval/oracle/actualArtifactResolution.js +73 -0
  198. package/dist/eval/oracle/actualArtifactResolution.js.map +1 -0
  199. package/dist/eval/oracle/cli.d.ts +30 -0
  200. package/dist/eval/oracle/cli.d.ts.map +1 -0
  201. package/dist/eval/oracle/cli.js +179 -0
  202. package/dist/eval/oracle/cli.js.map +1 -0
  203. package/dist/eval/oracle/diff.d.ts +26 -0
  204. package/dist/eval/oracle/diff.d.ts.map +1 -0
  205. package/dist/eval/oracle/diff.js +51 -0
  206. package/dist/eval/oracle/diff.js.map +1 -0
  207. package/dist/eval/oracle/index.d.ts +81 -0
  208. package/dist/eval/oracle/index.d.ts.map +1 -0
  209. package/dist/eval/oracle/index.js +55 -0
  210. package/dist/eval/oracle/index.js.map +1 -0
  211. package/dist/eval/oracle/normalize.d.ts +85 -0
  212. package/dist/eval/oracle/normalize.d.ts.map +1 -0
  213. package/dist/eval/oracle/normalize.js +321 -0
  214. package/dist/eval/oracle/normalize.js.map +1 -0
  215. package/dist/eval/oracle/score.d.ts +26 -0
  216. package/dist/eval/oracle/score.d.ts.map +1 -0
  217. package/dist/eval/oracle/score.js +15 -0
  218. package/dist/eval/oracle/score.js.map +1 -0
  219. package/dist/eval/oracle/systest.d.ts +30 -0
  220. package/dist/eval/oracle/systest.d.ts.map +1 -0
  221. package/dist/eval/oracle/systest.js +98 -0
  222. package/dist/eval/oracle/systest.js.map +1 -0
  223. package/dist/index.d.ts +6 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +753 -0
  226. package/dist/index.js.map +1 -0
  227. package/dist/knowledge/formPatterns/catalog/subPatterns/customFilters.d.ts +10 -0
  228. package/dist/knowledge/formPatterns/catalog/subPatterns/customFilters.d.ts.map +1 -0
  229. package/dist/knowledge/formPatterns/catalog/subPatterns/customFilters.js +38 -0
  230. package/dist/knowledge/formPatterns/catalog/subPatterns/customFilters.js.map +1 -0
  231. package/dist/knowledge/formPatterns/catalog/subPatterns/fields.d.ts +21 -0
  232. package/dist/knowledge/formPatterns/catalog/subPatterns/fields.d.ts.map +1 -0
  233. package/dist/knowledge/formPatterns/catalog/subPatterns/fields.js +168 -0
  234. package/dist/knowledge/formPatterns/catalog/subPatterns/fields.js.map +1 -0
  235. package/dist/knowledge/formPatterns/catalog/subPatterns/panels.d.ts +9 -0
  236. package/dist/knowledge/formPatterns/catalog/subPatterns/panels.d.ts.map +1 -0
  237. package/dist/knowledge/formPatterns/catalog/subPatterns/panels.js +47 -0
  238. package/dist/knowledge/formPatterns/catalog/subPatterns/panels.js.map +1 -0
  239. package/dist/knowledge/formPatterns/catalog/subPatterns/toolbar.d.ts +16 -0
  240. package/dist/knowledge/formPatterns/catalog/subPatterns/toolbar.d.ts.map +1 -0
  241. package/dist/knowledge/formPatterns/catalog/subPatterns/toolbar.js +124 -0
  242. package/dist/knowledge/formPatterns/catalog/subPatterns/toolbar.js.map +1 -0
  243. package/dist/knowledge/formPatterns/catalog/subPatterns/workspaceSections.d.ts +12 -0
  244. package/dist/knowledge/formPatterns/catalog/subPatterns/workspaceSections.d.ts.map +1 -0
  245. package/dist/knowledge/formPatterns/catalog/subPatterns/workspaceSections.js +36 -0
  246. package/dist/knowledge/formPatterns/catalog/subPatterns/workspaceSections.js.map +1 -0
  247. package/dist/knowledge/formPatterns/catalog/topLevel/common.d.ts +11 -0
  248. package/dist/knowledge/formPatterns/catalog/topLevel/common.d.ts.map +1 -0
  249. package/dist/knowledge/formPatterns/catalog/topLevel/common.js +37 -0
  250. package/dist/knowledge/formPatterns/catalog/topLevel/common.js.map +1 -0
  251. package/dist/knowledge/formPatterns/catalog/topLevel/detailsMaster.d.ts +8 -0
  252. package/dist/knowledge/formPatterns/catalog/topLevel/detailsMaster.d.ts.map +1 -0
  253. package/dist/knowledge/formPatterns/catalog/topLevel/detailsMaster.js +86 -0
  254. package/dist/knowledge/formPatterns/catalog/topLevel/detailsMaster.js.map +1 -0
  255. package/dist/knowledge/formPatterns/catalog/topLevel/detailsTransaction.d.ts +7 -0
  256. package/dist/knowledge/formPatterns/catalog/topLevel/detailsTransaction.d.ts.map +1 -0
  257. package/dist/knowledge/formPatterns/catalog/topLevel/detailsTransaction.js +69 -0
  258. package/dist/knowledge/formPatterns/catalog/topLevel/detailsTransaction.js.map +1 -0
  259. package/dist/knowledge/formPatterns/catalog/topLevel/dialog.d.ts +14 -0
  260. package/dist/knowledge/formPatterns/catalog/topLevel/dialog.d.ts.map +1 -0
  261. package/dist/knowledge/formPatterns/catalog/topLevel/dialog.js +187 -0
  262. package/dist/knowledge/formPatterns/catalog/topLevel/dialog.js.map +1 -0
  263. package/dist/knowledge/formPatterns/catalog/topLevel/factBox.d.ts +10 -0
  264. package/dist/knowledge/formPatterns/catalog/topLevel/factBox.d.ts.map +1 -0
  265. package/dist/knowledge/formPatterns/catalog/topLevel/factBox.js +54 -0
  266. package/dist/knowledge/formPatterns/catalog/topLevel/factBox.js.map +1 -0
  267. package/dist/knowledge/formPatterns/catalog/topLevel/listPage.d.ts +7 -0
  268. package/dist/knowledge/formPatterns/catalog/topLevel/listPage.d.ts.map +1 -0
  269. package/dist/knowledge/formPatterns/catalog/topLevel/listPage.js +33 -0
  270. package/dist/knowledge/formPatterns/catalog/topLevel/listPage.js.map +1 -0
  271. package/dist/knowledge/formPatterns/catalog/topLevel/lookup.d.ts +10 -0
  272. package/dist/knowledge/formPatterns/catalog/topLevel/lookup.d.ts.map +1 -0
  273. package/dist/knowledge/formPatterns/catalog/topLevel/lookup.js +95 -0
  274. package/dist/knowledge/formPatterns/catalog/topLevel/lookup.js.map +1 -0
  275. package/dist/knowledge/formPatterns/catalog/topLevel/simpleDetails.d.ts +16 -0
  276. package/dist/knowledge/formPatterns/catalog/topLevel/simpleDetails.d.ts.map +1 -0
  277. package/dist/knowledge/formPatterns/catalog/topLevel/simpleDetails.js +158 -0
  278. package/dist/knowledge/formPatterns/catalog/topLevel/simpleDetails.js.map +1 -0
  279. package/dist/knowledge/formPatterns/catalog/topLevel/simpleList.d.ts +7 -0
  280. package/dist/knowledge/formPatterns/catalog/topLevel/simpleList.d.ts.map +1 -0
  281. package/dist/knowledge/formPatterns/catalog/topLevel/simpleList.js +34 -0
  282. package/dist/knowledge/formPatterns/catalog/topLevel/simpleList.js.map +1 -0
  283. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.d.ts +9 -0
  284. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.d.ts.map +1 -0
  285. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.js +99 -0
  286. package/dist/knowledge/formPatterns/catalog/topLevel/simpleListDetails.js.map +1 -0
  287. package/dist/knowledge/formPatterns/catalog/topLevel/tableOfContents.d.ts +8 -0
  288. package/dist/knowledge/formPatterns/catalog/topLevel/tableOfContents.d.ts.map +1 -0
  289. package/dist/knowledge/formPatterns/catalog/topLevel/tableOfContents.js +95 -0
  290. package/dist/knowledge/formPatterns/catalog/topLevel/tableOfContents.js.map +1 -0
  291. package/dist/knowledge/formPatterns/catalog/topLevel/task.d.ts +9 -0
  292. package/dist/knowledge/formPatterns/catalog/topLevel/task.d.ts.map +1 -0
  293. package/dist/knowledge/formPatterns/catalog/topLevel/task.js +42 -0
  294. package/dist/knowledge/formPatterns/catalog/topLevel/task.js.map +1 -0
  295. package/dist/knowledge/formPatterns/catalog/topLevel/wizard.d.ts +7 -0
  296. package/dist/knowledge/formPatterns/catalog/topLevel/wizard.d.ts.map +1 -0
  297. package/dist/knowledge/formPatterns/catalog/topLevel/wizard.js +42 -0
  298. package/dist/knowledge/formPatterns/catalog/topLevel/wizard.js.map +1 -0
  299. package/dist/knowledge/formPatterns/catalog/topLevel/workspace.d.ts +17 -0
  300. package/dist/knowledge/formPatterns/catalog/topLevel/workspace.d.ts.map +1 -0
  301. package/dist/knowledge/formPatterns/catalog/topLevel/workspace.js +157 -0
  302. package/dist/knowledge/formPatterns/catalog/topLevel/workspace.js.map +1 -0
  303. package/dist/knowledge/formPatterns/crossCheck.d.ts +44 -0
  304. package/dist/knowledge/formPatterns/crossCheck.d.ts.map +1 -0
  305. package/dist/knowledge/formPatterns/crossCheck.js +109 -0
  306. package/dist/knowledge/formPatterns/crossCheck.js.map +1 -0
  307. package/dist/knowledge/formPatterns/index.d.ts +30 -0
  308. package/dist/knowledge/formPatterns/index.d.ts.map +1 -0
  309. package/dist/knowledge/formPatterns/index.js +158 -0
  310. package/dist/knowledge/formPatterns/index.js.map +1 -0
  311. package/dist/knowledge/formPatterns/methodStubs.d.ts +45 -0
  312. package/dist/knowledge/formPatterns/methodStubs.d.ts.map +1 -0
  313. package/dist/knowledge/formPatterns/methodStubs.js +257 -0
  314. package/dist/knowledge/formPatterns/methodStubs.js.map +1 -0
  315. package/dist/knowledge/formPatterns/types.d.ts +115 -0
  316. package/dist/knowledge/formPatterns/types.d.ts.map +1 -0
  317. package/dist/knowledge/formPatterns/types.js +44 -0
  318. package/dist/knowledge/formPatterns/types.js.map +1 -0
  319. package/dist/metadata/enhancedParser.d.ts +91 -0
  320. package/dist/metadata/enhancedParser.d.ts.map +1 -0
  321. package/dist/metadata/enhancedParser.js +379 -0
  322. package/dist/metadata/enhancedParser.js.map +1 -0
  323. package/dist/metadata/formPatternMiner.d.ts +78 -0
  324. package/dist/metadata/formPatternMiner.d.ts.map +1 -0
  325. package/dist/metadata/formPatternMiner.js +220 -0
  326. package/dist/metadata/formPatternMiner.js.map +1 -0
  327. package/dist/metadata/labelParser.d.ts +71 -0
  328. package/dist/metadata/labelParser.d.ts.map +1 -0
  329. package/dist/metadata/labelParser.js +258 -0
  330. package/dist/metadata/labelParser.js.map +1 -0
  331. package/dist/metadata/symbolCountsWorker.d.ts +16 -0
  332. package/dist/metadata/symbolCountsWorker.d.ts.map +1 -0
  333. package/dist/metadata/symbolCountsWorker.js +42 -0
  334. package/dist/metadata/symbolCountsWorker.js.map +1 -0
  335. package/dist/metadata/symbolIndex.d.ts +474 -0
  336. package/dist/metadata/symbolIndex.d.ts.map +1 -0
  337. package/dist/metadata/symbolIndex.js +3152 -0
  338. package/dist/metadata/symbolIndex.js.map +1 -0
  339. package/dist/metadata/types.d.ts +267 -0
  340. package/dist/metadata/types.d.ts.map +1 -0
  341. package/dist/metadata/types.js +5 -0
  342. package/dist/metadata/types.js.map +1 -0
  343. package/dist/metadata/xmlParser.d.ts +232 -0
  344. package/dist/metadata/xmlParser.d.ts.map +1 -0
  345. package/dist/metadata/xmlParser.js +1021 -0
  346. package/dist/metadata/xmlParser.js.map +1 -0
  347. package/dist/metadata/xppDeclaration.d.ts +116 -0
  348. package/dist/metadata/xppDeclaration.d.ts.map +1 -0
  349. package/dist/metadata/xppDeclaration.js +350 -0
  350. package/dist/metadata/xppDeclaration.js.map +1 -0
  351. package/dist/middleware/apiKeyAuth.d.ts +7 -0
  352. package/dist/middleware/apiKeyAuth.d.ts.map +1 -0
  353. package/dist/middleware/apiKeyAuth.js +70 -0
  354. package/dist/middleware/apiKeyAuth.js.map +1 -0
  355. package/dist/middleware/rateLimiter.d.ts +7 -0
  356. package/dist/middleware/rateLimiter.d.ts.map +1 -0
  357. package/dist/middleware/rateLimiter.js +78 -0
  358. package/dist/middleware/rateLimiter.js.map +1 -0
  359. package/dist/prompts/codeReview.d.ts +8 -0
  360. package/dist/prompts/codeReview.d.ts.map +1 -0
  361. package/dist/prompts/codeReview.js +619 -0
  362. package/dist/prompts/codeReview.js.map +1 -0
  363. package/dist/prompts/index.d.ts +6 -0
  364. package/dist/prompts/index.d.ts.map +1 -0
  365. package/dist/prompts/index.js +6 -0
  366. package/dist/prompts/index.js.map +1 -0
  367. package/dist/prompts/systemInstructions.d.ts +33 -0
  368. package/dist/prompts/systemInstructions.d.ts.map +1 -0
  369. package/dist/prompts/systemInstructions.js +154 -0
  370. package/dist/prompts/systemInstructions.js.map +1 -0
  371. package/dist/resources/classResource.d.ts +17 -0
  372. package/dist/resources/classResource.d.ts.map +1 -0
  373. package/dist/resources/classResource.js +33 -0
  374. package/dist/resources/classResource.js.map +1 -0
  375. package/dist/resources/index.d.ts +20 -0
  376. package/dist/resources/index.d.ts.map +1 -0
  377. package/dist/resources/index.js +154 -0
  378. package/dist/resources/index.js.map +1 -0
  379. package/dist/server/mcpServer.d.ts +11 -0
  380. package/dist/server/mcpServer.d.ts.map +1 -0
  381. package/dist/server/mcpServer.js +185 -0
  382. package/dist/server/mcpServer.js.map +1 -0
  383. package/dist/server/serverMode.d.ts +53 -0
  384. package/dist/server/serverMode.d.ts.map +1 -0
  385. package/dist/server/serverMode.js +72 -0
  386. package/dist/server/serverMode.js.map +1 -0
  387. package/dist/server/toolAnnotations.d.ts +28 -0
  388. package/dist/server/toolAnnotations.d.ts.map +1 -0
  389. package/dist/server/toolAnnotations.js +70 -0
  390. package/dist/server/toolAnnotations.js.map +1 -0
  391. package/dist/server/toolSchemas/analyzeCode.d.ts +71 -0
  392. package/dist/server/toolSchemas/analyzeCode.d.ts.map +1 -0
  393. package/dist/server/toolSchemas/analyzeCode.js +50 -0
  394. package/dist/server/toolSchemas/analyzeCode.js.map +1 -0
  395. package/dist/server/toolSchemas/batchGetInfo.d.ts +32 -0
  396. package/dist/server/toolSchemas/batchGetInfo.d.ts.map +1 -0
  397. package/dist/server/toolSchemas/batchGetInfo.js +35 -0
  398. package/dist/server/toolSchemas/batchGetInfo.js.map +1 -0
  399. package/dist/server/toolSchemas/buildD365foProject.d.ts +44 -0
  400. package/dist/server/toolSchemas/buildD365foProject.d.ts.map +1 -0
  401. package/dist/server/toolSchemas/buildD365foProject.js +47 -0
  402. package/dist/server/toolSchemas/buildD365foProject.js.map +1 -0
  403. package/dist/server/toolSchemas/d365foFile.d.ts +99 -0
  404. package/dist/server/toolSchemas/d365foFile.d.ts.map +1 -0
  405. package/dist/server/toolSchemas/d365foFile.js +163 -0
  406. package/dist/server/toolSchemas/d365foFile.js.map +1 -0
  407. package/dist/server/toolSchemas/extensionInfo.d.ts +65 -0
  408. package/dist/server/toolSchemas/extensionInfo.d.ts.map +1 -0
  409. package/dist/server/toolSchemas/extensionInfo.js +73 -0
  410. package/dist/server/toolSchemas/extensionInfo.js.map +1 -0
  411. package/dist/server/toolSchemas/findReferences.d.ts +35 -0
  412. package/dist/server/toolSchemas/findReferences.d.ts.map +1 -0
  413. package/dist/server/toolSchemas/findReferences.js +35 -0
  414. package/dist/server/toolSchemas/findReferences.js.map +1 -0
  415. package/dist/server/toolSchemas/generateObject.d.ts +246 -0
  416. package/dist/server/toolSchemas/generateObject.d.ts.map +1 -0
  417. package/dist/server/toolSchemas/generateObject.js +158 -0
  418. package/dist/server/toolSchemas/generateObject.js.map +1 -0
  419. package/dist/server/toolSchemas/getKnowledge.d.ts +39 -0
  420. package/dist/server/toolSchemas/getKnowledge.d.ts.map +1 -0
  421. package/dist/server/toolSchemas/getKnowledge.js +46 -0
  422. package/dist/server/toolSchemas/getKnowledge.js.map +1 -0
  423. package/dist/server/toolSchemas/getMethod.d.ts +30 -0
  424. package/dist/server/toolSchemas/getMethod.d.ts.map +1 -0
  425. package/dist/server/toolSchemas/getMethod.js +34 -0
  426. package/dist/server/toolSchemas/getMethod.js.map +1 -0
  427. package/dist/server/toolSchemas/getObjectInfo.d.ts +24 -0
  428. package/dist/server/toolSchemas/getObjectInfo.d.ts.map +1 -0
  429. package/dist/server/toolSchemas/getObjectInfo.js +30 -0
  430. package/dist/server/toolSchemas/getObjectInfo.js.map +1 -0
  431. package/dist/server/toolSchemas/getWorkspaceInfo.d.ts +29 -0
  432. package/dist/server/toolSchemas/getWorkspaceInfo.d.ts.map +1 -0
  433. package/dist/server/toolSchemas/getWorkspaceInfo.js +29 -0
  434. package/dist/server/toolSchemas/getWorkspaceInfo.js.map +1 -0
  435. package/dist/server/toolSchemas/index.d.ts +1142 -0
  436. package/dist/server/toolSchemas/index.d.ts.map +1 -0
  437. package/dist/server/toolSchemas/index.js +60 -0
  438. package/dist/server/toolSchemas/index.js.map +1 -0
  439. package/dist/server/toolSchemas/labels.d.ts +172 -0
  440. package/dist/server/toolSchemas/labels.d.ts.map +1 -0
  441. package/dist/server/toolSchemas/labels.js +159 -0
  442. package/dist/server/toolSchemas/labels.js.map +1 -0
  443. package/dist/server/toolSchemas/objectPatterns.d.ts +93 -0
  444. package/dist/server/toolSchemas/objectPatterns.d.ts.map +1 -0
  445. package/dist/server/toolSchemas/objectPatterns.js +104 -0
  446. package/dist/server/toolSchemas/objectPatterns.js.map +1 -0
  447. package/dist/server/toolSchemas/prepare.d.ts +49 -0
  448. package/dist/server/toolSchemas/prepare.d.ts.map +1 -0
  449. package/dist/server/toolSchemas/prepare.js +57 -0
  450. package/dist/server/toolSchemas/prepare.js.map +1 -0
  451. package/dist/server/toolSchemas/reviewWorkspaceChanges.d.ts +20 -0
  452. package/dist/server/toolSchemas/reviewWorkspaceChanges.d.ts.map +1 -0
  453. package/dist/server/toolSchemas/reviewWorkspaceChanges.js +19 -0
  454. package/dist/server/toolSchemas/reviewWorkspaceChanges.js.map +1 -0
  455. package/dist/server/toolSchemas/runBpCheck.d.ts +36 -0
  456. package/dist/server/toolSchemas/runBpCheck.d.ts.map +1 -0
  457. package/dist/server/toolSchemas/runBpCheck.js +21 -0
  458. package/dist/server/toolSchemas/runBpCheck.js.map +1 -0
  459. package/dist/server/toolSchemas/runSystestClass.d.ts +32 -0
  460. package/dist/server/toolSchemas/runSystestClass.d.ts.map +1 -0
  461. package/dist/server/toolSchemas/runSystestClass.js +20 -0
  462. package/dist/server/toolSchemas/runSystestClass.js.map +1 -0
  463. package/dist/server/toolSchemas/search.d.ts +109 -0
  464. package/dist/server/toolSchemas/search.d.ts.map +1 -0
  465. package/dist/server/toolSchemas/search.js +123 -0
  466. package/dist/server/toolSchemas/search.js.map +1 -0
  467. package/dist/server/toolSchemas/securityInfo.d.ts +45 -0
  468. package/dist/server/toolSchemas/securityInfo.d.ts.map +1 -0
  469. package/dist/server/toolSchemas/securityInfo.js +39 -0
  470. package/dist/server/toolSchemas/securityInfo.js.map +1 -0
  471. package/dist/server/toolSchemas/suggestEdt.d.ts +29 -0
  472. package/dist/server/toolSchemas/suggestEdt.d.ts.map +1 -0
  473. package/dist/server/toolSchemas/suggestEdt.js +29 -0
  474. package/dist/server/toolSchemas/suggestEdt.js.map +1 -0
  475. package/dist/server/toolSchemas/triggerDbSync.d.ts +47 -0
  476. package/dist/server/toolSchemas/triggerDbSync.d.ts.map +1 -0
  477. package/dist/server/toolSchemas/triggerDbSync.js +31 -0
  478. package/dist/server/toolSchemas/triggerDbSync.js.map +1 -0
  479. package/dist/server/toolSchemas/undoLastModification.d.ts +20 -0
  480. package/dist/server/toolSchemas/undoLastModification.d.ts.map +1 -0
  481. package/dist/server/toolSchemas/undoLastModification.js +17 -0
  482. package/dist/server/toolSchemas/undoLastModification.js.map +1 -0
  483. package/dist/server/toolSchemas/updateSymbolIndex.d.ts +19 -0
  484. package/dist/server/toolSchemas/updateSymbolIndex.d.ts.map +1 -0
  485. package/dist/server/toolSchemas/updateSymbolIndex.js +18 -0
  486. package/dist/server/toolSchemas/updateSymbolIndex.js.map +1 -0
  487. package/dist/server/toolSchemas/validateCode.d.ts +35 -0
  488. package/dist/server/toolSchemas/validateCode.d.ts.map +1 -0
  489. package/dist/server/toolSchemas/validateCode.js +38 -0
  490. package/dist/server/toolSchemas/validateCode.js.map +1 -0
  491. package/dist/server/toolSchemas/validateObjectNaming.d.ts +33 -0
  492. package/dist/server/toolSchemas/validateObjectNaming.d.ts.map +1 -0
  493. package/dist/server/toolSchemas/validateObjectNaming.js +32 -0
  494. package/dist/server/toolSchemas/validateObjectNaming.js.map +1 -0
  495. package/dist/server/toolSchemas/verifyD365foProject.d.ts +50 -0
  496. package/dist/server/toolSchemas/verifyD365foProject.d.ts.map +1 -0
  497. package/dist/server/toolSchemas/verifyD365foProject.js +46 -0
  498. package/dist/server/toolSchemas/verifyD365foProject.js.map +1 -0
  499. package/dist/server/transport.d.ts +30 -0
  500. package/dist/server/transport.d.ts.map +1 -0
  501. package/dist/server/transport.js +391 -0
  502. package/dist/server/transport.js.map +1 -0
  503. package/dist/tools/analyzeCode.d.ts +17 -0
  504. package/dist/tools/analyzeCode.d.ts.map +1 -0
  505. package/dist/tools/analyzeCode.js +63 -0
  506. package/dist/tools/analyzeCode.js.map +1 -0
  507. package/dist/tools/analyzeCompleteness.d.ts +20 -0
  508. package/dist/tools/analyzeCompleteness.d.ts.map +1 -0
  509. package/dist/tools/analyzeCompleteness.js +103 -0
  510. package/dist/tools/analyzeCompleteness.js.map +1 -0
  511. package/dist/tools/analyzeExtensionPoints.d.ts +21 -0
  512. package/dist/tools/analyzeExtensionPoints.d.ts.map +1 -0
  513. package/dist/tools/analyzeExtensionPoints.js +343 -0
  514. package/dist/tools/analyzeExtensionPoints.js.map +1 -0
  515. package/dist/tools/analyzePatterns.d.ts +20 -0
  516. package/dist/tools/analyzePatterns.d.ts.map +1 -0
  517. package/dist/tools/analyzePatterns.js +71 -0
  518. package/dist/tools/analyzePatterns.js.map +1 -0
  519. package/dist/tools/apiUsagePatterns.d.ts +20 -0
  520. package/dist/tools/apiUsagePatterns.d.ts.map +1 -0
  521. package/dist/tools/apiUsagePatterns.js +136 -0
  522. package/dist/tools/apiUsagePatterns.js.map +1 -0
  523. package/dist/tools/batchGetInfo.d.ts +50 -0
  524. package/dist/tools/batchGetInfo.d.ts.map +1 -0
  525. package/dist/tools/batchGetInfo.js +52 -0
  526. package/dist/tools/batchGetInfo.js.map +1 -0
  527. package/dist/tools/batchSearch.d.ts +64 -0
  528. package/dist/tools/batchSearch.d.ts.map +1 -0
  529. package/dist/tools/batchSearch.js +265 -0
  530. package/dist/tools/batchSearch.js.map +1 -0
  531. package/dist/tools/buildProject.d.ts +28 -0
  532. package/dist/tools/buildProject.d.ts.map +1 -0
  533. package/dist/tools/buildProject.js +929 -0
  534. package/dist/tools/buildProject.js.map +1 -0
  535. package/dist/tools/classInfo.d.ts +8 -0
  536. package/dist/tools/classInfo.d.ts.map +1 -0
  537. package/dist/tools/classInfo.js +235 -0
  538. package/dist/tools/classInfo.js.map +1 -0
  539. package/dist/tools/codeGen.d.ts +19 -0
  540. package/dist/tools/codeGen.d.ts.map +1 -0
  541. package/dist/tools/codeGen.js +1956 -0
  542. package/dist/tools/codeGen.js.map +1 -0
  543. package/dist/tools/completion.d.ts +8 -0
  544. package/dist/tools/completion.d.ts.map +1 -0
  545. package/dist/tools/completion.js +272 -0
  546. package/dist/tools/completion.js.map +1 -0
  547. package/dist/tools/configKeyInfo.d.ts +22 -0
  548. package/dist/tools/configKeyInfo.d.ts.map +1 -0
  549. package/dist/tools/configKeyInfo.js +74 -0
  550. package/dist/tools/configKeyInfo.js.map +1 -0
  551. package/dist/tools/createD365File.d.ts +479 -0
  552. package/dist/tools/createD365File.d.ts.map +1 -0
  553. package/dist/tools/createD365File.js +4185 -0
  554. package/dist/tools/createD365File.js.map +1 -0
  555. package/dist/tools/createLabel.d.ts +51 -0
  556. package/dist/tools/createLabel.d.ts.map +1 -0
  557. package/dist/tools/createLabel.js +761 -0
  558. package/dist/tools/createLabel.js.map +1 -0
  559. package/dist/tools/d365foErrorHelp.d.ts +29 -0
  560. package/dist/tools/d365foErrorHelp.d.ts.map +1 -0
  561. package/dist/tools/d365foErrorHelp.js +389 -0
  562. package/dist/tools/d365foErrorHelp.js.map +1 -0
  563. package/dist/tools/d365foFile.d.ts +20 -0
  564. package/dist/tools/d365foFile.d.ts.map +1 -0
  565. package/dist/tools/d365foFile.js +58 -0
  566. package/dist/tools/d365foFile.js.map +1 -0
  567. package/dist/tools/d365foFileOpSpecs.d.ts +41 -0
  568. package/dist/tools/d365foFileOpSpecs.d.ts.map +1 -0
  569. package/dist/tools/d365foFileOpSpecs.js +336 -0
  570. package/dist/tools/d365foFileOpSpecs.js.map +1 -0
  571. package/dist/tools/dataEntityInfo.d.ts +23 -0
  572. package/dist/tools/dataEntityInfo.d.ts.map +1 -0
  573. package/dist/tools/dataEntityInfo.js +45 -0
  574. package/dist/tools/dataEntityInfo.js.map +1 -0
  575. package/dist/tools/dataEntityXml.d.ts +31 -0
  576. package/dist/tools/dataEntityXml.d.ts.map +1 -0
  577. package/dist/tools/dataEntityXml.js +131 -0
  578. package/dist/tools/dataEntityXml.js.map +1 -0
  579. package/dist/tools/dbSync.d.ts +8 -0
  580. package/dist/tools/dbSync.d.ts.map +1 -0
  581. package/dist/tools/dbSync.js +285 -0
  582. package/dist/tools/dbSync.js.map +1 -0
  583. package/dist/tools/edtInfo.d.ts +23 -0
  584. package/dist/tools/edtInfo.d.ts.map +1 -0
  585. package/dist/tools/edtInfo.js +122 -0
  586. package/dist/tools/edtInfo.js.map +1 -0
  587. package/dist/tools/enumInfo.d.ts +17 -0
  588. package/dist/tools/enumInfo.d.ts.map +1 -0
  589. package/dist/tools/enumInfo.js +44 -0
  590. package/dist/tools/enumInfo.js.map +1 -0
  591. package/dist/tools/extensionInfo.d.ts +26 -0
  592. package/dist/tools/extensionInfo.d.ts.map +1 -0
  593. package/dist/tools/extensionInfo.js +73 -0
  594. package/dist/tools/extensionInfo.js.map +1 -0
  595. package/dist/tools/extensionSearch.d.ts +20 -0
  596. package/dist/tools/extensionSearch.d.ts.map +1 -0
  597. package/dist/tools/extensionSearch.js +72 -0
  598. package/dist/tools/extensionSearch.js.map +1 -0
  599. package/dist/tools/extensionStrategyAdvisor.d.ts +15 -0
  600. package/dist/tools/extensionStrategyAdvisor.d.ts.map +1 -0
  601. package/dist/tools/extensionStrategyAdvisor.js +519 -0
  602. package/dist/tools/extensionStrategyAdvisor.js.map +1 -0
  603. package/dist/tools/findCocExtensions.d.ts +20 -0
  604. package/dist/tools/findCocExtensions.d.ts.map +1 -0
  605. package/dist/tools/findCocExtensions.js +285 -0
  606. package/dist/tools/findCocExtensions.js.map +1 -0
  607. package/dist/tools/findEventHandlers.d.ts +21 -0
  608. package/dist/tools/findEventHandlers.d.ts.map +1 -0
  609. package/dist/tools/findEventHandlers.js +196 -0
  610. package/dist/tools/findEventHandlers.js.map +1 -0
  611. package/dist/tools/findReferences.d.ts +30 -0
  612. package/dist/tools/findReferences.d.ts.map +1 -0
  613. package/dist/tools/findReferences.js +551 -0
  614. package/dist/tools/findReferences.js.map +1 -0
  615. package/dist/tools/formInfo.d.ts +13 -0
  616. package/dist/tools/formInfo.d.ts.map +1 -0
  617. package/dist/tools/formInfo.js +407 -0
  618. package/dist/tools/formInfo.js.map +1 -0
  619. package/dist/tools/formPattern.d.ts +19 -0
  620. package/dist/tools/formPattern.d.ts.map +1 -0
  621. package/dist/tools/formPattern.js +52 -0
  622. package/dist/tools/formPattern.js.map +1 -0
  623. package/dist/tools/generateD365Xml.d.ts +12 -0
  624. package/dist/tools/generateD365Xml.d.ts.map +1 -0
  625. package/dist/tools/generateD365Xml.js +1454 -0
  626. package/dist/tools/generateD365Xml.js.map +1 -0
  627. package/dist/tools/generateFindMethods.d.ts +69 -0
  628. package/dist/tools/generateFindMethods.d.ts.map +1 -0
  629. package/dist/tools/generateFindMethods.js +207 -0
  630. package/dist/tools/generateFindMethods.js.map +1 -0
  631. package/dist/tools/generateMetadata.d.ts +37 -0
  632. package/dist/tools/generateMetadata.d.ts.map +1 -0
  633. package/dist/tools/generateMetadata.js +213 -0
  634. package/dist/tools/generateMetadata.js.map +1 -0
  635. package/dist/tools/generateObject.d.ts +34 -0
  636. package/dist/tools/generateObject.d.ts.map +1 -0
  637. package/dist/tools/generateObject.js +47 -0
  638. package/dist/tools/generateObject.js.map +1 -0
  639. package/dist/tools/generateRelationXpp.d.ts +45 -0
  640. package/dist/tools/generateRelationXpp.d.ts.map +1 -0
  641. package/dist/tools/generateRelationXpp.js +129 -0
  642. package/dist/tools/generateRelationXpp.js.map +1 -0
  643. package/dist/tools/generateSmart.d.ts +32 -0
  644. package/dist/tools/generateSmart.d.ts.map +1 -0
  645. package/dist/tools/generateSmart.js +52 -0
  646. package/dist/tools/generateSmart.js.map +1 -0
  647. package/dist/tools/generateSmartForm.d.ts +82 -0
  648. package/dist/tools/generateSmartForm.d.ts.map +1 -0
  649. package/dist/tools/generateSmartForm.js +996 -0
  650. package/dist/tools/generateSmartForm.js.map +1 -0
  651. package/dist/tools/generateSmartReport.d.ts +90 -0
  652. package/dist/tools/generateSmartReport.d.ts.map +1 -0
  653. package/dist/tools/generateSmartReport.js +1254 -0
  654. package/dist/tools/generateSmartReport.js.map +1 -0
  655. package/dist/tools/generateSmartTable.d.ts +138 -0
  656. package/dist/tools/generateSmartTable.d.ts.map +1 -0
  657. package/dist/tools/generateSmartTable.js +1305 -0
  658. package/dist/tools/generateSmartTable.js.map +1 -0
  659. package/dist/tools/generateTableFields.d.ts +48 -0
  660. package/dist/tools/generateTableFields.d.ts.map +1 -0
  661. package/dist/tools/generateTableFields.js +186 -0
  662. package/dist/tools/generateTableFields.js.map +1 -0
  663. package/dist/tools/generateTableRelation.d.ts +35 -0
  664. package/dist/tools/generateTableRelation.d.ts.map +1 -0
  665. package/dist/tools/generateTableRelation.js +161 -0
  666. package/dist/tools/generateTableRelation.js.map +1 -0
  667. package/dist/tools/getFormPatternSpec.d.ts +13 -0
  668. package/dist/tools/getFormPatternSpec.d.ts.map +1 -0
  669. package/dist/tools/getFormPatternSpec.js +290 -0
  670. package/dist/tools/getFormPatternSpec.js.map +1 -0
  671. package/dist/tools/getFormPatterns.d.ts +68 -0
  672. package/dist/tools/getFormPatterns.d.ts.map +1 -0
  673. package/dist/tools/getFormPatterns.js +471 -0
  674. package/dist/tools/getFormPatterns.js.map +1 -0
  675. package/dist/tools/getKnowledge.d.ts +27 -0
  676. package/dist/tools/getKnowledge.d.ts.map +1 -0
  677. package/dist/tools/getKnowledge.js +50 -0
  678. package/dist/tools/getKnowledge.js.map +1 -0
  679. package/dist/tools/getLabelInfo.d.ts +21 -0
  680. package/dist/tools/getLabelInfo.d.ts.map +1 -0
  681. package/dist/tools/getLabelInfo.js +138 -0
  682. package/dist/tools/getLabelInfo.js.map +1 -0
  683. package/dist/tools/getMethod.d.ts +19 -0
  684. package/dist/tools/getMethod.d.ts.map +1 -0
  685. package/dist/tools/getMethod.js +77 -0
  686. package/dist/tools/getMethod.js.map +1 -0
  687. package/dist/tools/getMethodSource.d.ts +17 -0
  688. package/dist/tools/getMethodSource.d.ts.map +1 -0
  689. package/dist/tools/getMethodSource.js +161 -0
  690. package/dist/tools/getMethodSource.js.map +1 -0
  691. package/dist/tools/getObjectInfo.d.ts +17 -0
  692. package/dist/tools/getObjectInfo.d.ts.map +1 -0
  693. package/dist/tools/getObjectInfo.js +88 -0
  694. package/dist/tools/getObjectInfo.js.map +1 -0
  695. package/dist/tools/getTablePatterns.d.ts +45 -0
  696. package/dist/tools/getTablePatterns.d.ts.map +1 -0
  697. package/dist/tools/getTablePatterns.js +235 -0
  698. package/dist/tools/getTablePatterns.js.map +1 -0
  699. package/dist/tools/index.d.ts +11 -0
  700. package/dist/tools/index.d.ts.map +1 -0
  701. package/dist/tools/index.js +11 -0
  702. package/dist/tools/index.js.map +1 -0
  703. package/dist/tools/labels.d.ts +25 -0
  704. package/dist/tools/labels.d.ts.map +1 -0
  705. package/dist/tools/labels.js +111 -0
  706. package/dist/tools/labels.js.map +1 -0
  707. package/dist/tools/macroInfo.d.ts +23 -0
  708. package/dist/tools/macroInfo.d.ts.map +1 -0
  709. package/dist/tools/macroInfo.js +62 -0
  710. package/dist/tools/macroInfo.js.map +1 -0
  711. package/dist/tools/mapInfo.d.ts +21 -0
  712. package/dist/tools/mapInfo.d.ts.map +1 -0
  713. package/dist/tools/mapInfo.js +54 -0
  714. package/dist/tools/mapInfo.js.map +1 -0
  715. package/dist/tools/mapXml.d.ts +26 -0
  716. package/dist/tools/mapXml.d.ts.map +1 -0
  717. package/dist/tools/mapXml.js +88 -0
  718. package/dist/tools/mapXml.js.map +1 -0
  719. package/dist/tools/menuItemInfo.d.ts +20 -0
  720. package/dist/tools/menuItemInfo.d.ts.map +1 -0
  721. package/dist/tools/menuItemInfo.js +185 -0
  722. package/dist/tools/menuItemInfo.js.map +1 -0
  723. package/dist/tools/methodSignature.d.ts +33 -0
  724. package/dist/tools/methodSignature.d.ts.map +1 -0
  725. package/dist/tools/methodSignature.js +348 -0
  726. package/dist/tools/methodSignature.js.map +1 -0
  727. package/dist/tools/modifyD365File.d.ts +130 -0
  728. package/dist/tools/modifyD365File.d.ts.map +1 -0
  729. package/dist/tools/modifyD365File.js +2179 -0
  730. package/dist/tools/modifyD365File.js.map +1 -0
  731. package/dist/tools/objectInfoRegistry.d.ts +36 -0
  732. package/dist/tools/objectInfoRegistry.d.ts.map +1 -0
  733. package/dist/tools/objectInfoRegistry.js +98 -0
  734. package/dist/tools/objectInfoRegistry.js.map +1 -0
  735. package/dist/tools/objectPatterns.d.ts +16 -0
  736. package/dist/tools/objectPatterns.d.ts.map +1 -0
  737. package/dist/tools/objectPatterns.js +84 -0
  738. package/dist/tools/objectPatterns.js.map +1 -0
  739. package/dist/tools/prepare.d.ts +18 -0
  740. package/dist/tools/prepare.d.ts.map +1 -0
  741. package/dist/tools/prepare.js +47 -0
  742. package/dist/tools/prepare.js.map +1 -0
  743. package/dist/tools/prepareChange.d.ts +43 -0
  744. package/dist/tools/prepareChange.d.ts.map +1 -0
  745. package/dist/tools/prepareChange.js +327 -0
  746. package/dist/tools/prepareChange.js.map +1 -0
  747. package/dist/tools/prepareCreate.d.ts +45 -0
  748. package/dist/tools/prepareCreate.d.ts.map +1 -0
  749. package/dist/tools/prepareCreate.js +263 -0
  750. package/dist/tools/prepareCreate.js.map +1 -0
  751. package/dist/tools/queryInfo.d.ts +17 -0
  752. package/dist/tools/queryInfo.d.ts.map +1 -0
  753. package/dist/tools/queryInfo.js +46 -0
  754. package/dist/tools/queryInfo.js.map +1 -0
  755. package/dist/tools/queryViewXml.d.ts +36 -0
  756. package/dist/tools/queryViewXml.d.ts.map +1 -0
  757. package/dist/tools/queryViewXml.js +129 -0
  758. package/dist/tools/queryViewXml.js.map +1 -0
  759. package/dist/tools/renameLabel.d.ts +29 -0
  760. package/dist/tools/renameLabel.d.ts.map +1 -0
  761. package/dist/tools/renameLabel.js +377 -0
  762. package/dist/tools/renameLabel.js.map +1 -0
  763. package/dist/tools/repairFormControls.d.ts +21 -0
  764. package/dist/tools/repairFormControls.d.ts.map +1 -0
  765. package/dist/tools/repairFormControls.js +151 -0
  766. package/dist/tools/repairFormControls.js.map +1 -0
  767. package/dist/tools/reportInfo.d.ts +14 -0
  768. package/dist/tools/reportInfo.d.ts.map +1 -0
  769. package/dist/tools/reportInfo.js +302 -0
  770. package/dist/tools/reportInfo.js.map +1 -0
  771. package/dist/tools/resolveReferences.d.ts +89 -0
  772. package/dist/tools/resolveReferences.d.ts.map +1 -0
  773. package/dist/tools/resolveReferences.js +791 -0
  774. package/dist/tools/resolveReferences.js.map +1 -0
  775. package/dist/tools/reviewWorkspaceChanges.d.ts +14 -0
  776. package/dist/tools/reviewWorkspaceChanges.d.ts.map +1 -0
  777. package/dist/tools/reviewWorkspaceChanges.js +73 -0
  778. package/dist/tools/reviewWorkspaceChanges.js.map +1 -0
  779. package/dist/tools/runBpCheck.d.ts +14 -0
  780. package/dist/tools/runBpCheck.d.ts.map +1 -0
  781. package/dist/tools/runBpCheck.js +241 -0
  782. package/dist/tools/runBpCheck.js.map +1 -0
  783. package/dist/tools/search.d.ts +20 -0
  784. package/dist/tools/search.d.ts.map +1 -0
  785. package/dist/tools/search.js +246 -0
  786. package/dist/tools/search.js.map +1 -0
  787. package/dist/tools/searchLabels.d.ts +26 -0
  788. package/dist/tools/searchLabels.d.ts.map +1 -0
  789. package/dist/tools/searchLabels.js +91 -0
  790. package/dist/tools/searchLabels.js.map +1 -0
  791. package/dist/tools/searchUnified.d.ts +28 -0
  792. package/dist/tools/searchUnified.d.ts.map +1 -0
  793. package/dist/tools/searchUnified.js +48 -0
  794. package/dist/tools/searchUnified.js.map +1 -0
  795. package/dist/tools/securityArtifactInfo.d.ts +14 -0
  796. package/dist/tools/securityArtifactInfo.d.ts.map +1 -0
  797. package/dist/tools/securityArtifactInfo.js +192 -0
  798. package/dist/tools/securityArtifactInfo.js.map +1 -0
  799. package/dist/tools/securityCoverageInfo.d.ts +21 -0
  800. package/dist/tools/securityCoverageInfo.d.ts.map +1 -0
  801. package/dist/tools/securityCoverageInfo.js +248 -0
  802. package/dist/tools/securityCoverageInfo.js.map +1 -0
  803. package/dist/tools/securityInfo.d.ts +22 -0
  804. package/dist/tools/securityInfo.d.ts.map +1 -0
  805. package/dist/tools/securityInfo.js +43 -0
  806. package/dist/tools/securityInfo.js.map +1 -0
  807. package/dist/tools/securityPolicyInfo.d.ts +22 -0
  808. package/dist/tools/securityPolicyInfo.d.ts.map +1 -0
  809. package/dist/tools/securityPolicyInfo.js +49 -0
  810. package/dist/tools/securityPolicyInfo.js.map +1 -0
  811. package/dist/tools/securityPrivilegeXml.d.ts +21 -0
  812. package/dist/tools/securityPrivilegeXml.d.ts.map +1 -0
  813. package/dist/tools/securityPrivilegeXml.js +61 -0
  814. package/dist/tools/securityPrivilegeXml.js.map +1 -0
  815. package/dist/tools/serviceInfo.d.ts +25 -0
  816. package/dist/tools/serviceInfo.d.ts.map +1 -0
  817. package/dist/tools/serviceInfo.js +93 -0
  818. package/dist/tools/serviceInfo.js.map +1 -0
  819. package/dist/tools/suggestEdt.d.ts +15 -0
  820. package/dist/tools/suggestEdt.d.ts.map +1 -0
  821. package/dist/tools/suggestEdt.js +348 -0
  822. package/dist/tools/suggestEdt.js.map +1 -0
  823. package/dist/tools/suggestImplementation.d.ts +20 -0
  824. package/dist/tools/suggestImplementation.d.ts.map +1 -0
  825. package/dist/tools/suggestImplementation.js +144 -0
  826. package/dist/tools/suggestImplementation.js.map +1 -0
  827. package/dist/tools/sysTestRunner.d.ts +14 -0
  828. package/dist/tools/sysTestRunner.d.ts.map +1 -0
  829. package/dist/tools/sysTestRunner.js +145 -0
  830. package/dist/tools/sysTestRunner.js.map +1 -0
  831. package/dist/tools/tableExtensionInfo.d.ts +91 -0
  832. package/dist/tools/tableExtensionInfo.d.ts.map +1 -0
  833. package/dist/tools/tableExtensionInfo.js +305 -0
  834. package/dist/tools/tableExtensionInfo.js.map +1 -0
  835. package/dist/tools/tableInfo.d.ts +23 -0
  836. package/dist/tools/tableInfo.d.ts.map +1 -0
  837. package/dist/tools/tableInfo.js +125 -0
  838. package/dist/tools/tableInfo.js.map +1 -0
  839. package/dist/tools/toolHandler.d.ts +4 -0
  840. package/dist/tools/toolHandler.d.ts.map +1 -0
  841. package/dist/tools/toolHandler.js +508 -0
  842. package/dist/tools/toolHandler.js.map +1 -0
  843. package/dist/tools/undoLastModification.d.ts +15 -0
  844. package/dist/tools/undoLastModification.d.ts.map +1 -0
  845. package/dist/tools/undoLastModification.js +147 -0
  846. package/dist/tools/undoLastModification.js.map +1 -0
  847. package/dist/tools/updateSymbolIndex.d.ts +15 -0
  848. package/dist/tools/updateSymbolIndex.d.ts.map +1 -0
  849. package/dist/tools/updateSymbolIndex.js +520 -0
  850. package/dist/tools/updateSymbolIndex.js.map +1 -0
  851. package/dist/tools/validateCode.d.ts +24 -0
  852. package/dist/tools/validateCode.d.ts.map +1 -0
  853. package/dist/tools/validateCode.js +201 -0
  854. package/dist/tools/validateCode.js.map +1 -0
  855. package/dist/tools/validateFormPattern.d.ts +51 -0
  856. package/dist/tools/validateFormPattern.d.ts.map +1 -0
  857. package/dist/tools/validateFormPattern.js +201 -0
  858. package/dist/tools/validateFormPattern.js.map +1 -0
  859. package/dist/tools/validateObjectNaming.d.ts +21 -0
  860. package/dist/tools/validateObjectNaming.d.ts.map +1 -0
  861. package/dist/tools/validateObjectNaming.js +319 -0
  862. package/dist/tools/validateObjectNaming.js.map +1 -0
  863. package/dist/tools/validateXpp.d.ts +79 -0
  864. package/dist/tools/validateXpp.d.ts.map +1 -0
  865. package/dist/tools/validateXpp.js +705 -0
  866. package/dist/tools/validateXpp.js.map +1 -0
  867. package/dist/tools/verifyD365Project.d.ts +21 -0
  868. package/dist/tools/verifyD365Project.d.ts.map +1 -0
  869. package/dist/tools/verifyD365Project.js +299 -0
  870. package/dist/tools/verifyD365Project.js.map +1 -0
  871. package/dist/tools/viewInfo.d.ts +17 -0
  872. package/dist/tools/viewInfo.d.ts.map +1 -0
  873. package/dist/tools/viewInfo.js +46 -0
  874. package/dist/tools/viewInfo.js.map +1 -0
  875. package/dist/tools/xppKnowledge.d.ts +45 -0
  876. package/dist/tools/xppKnowledge.d.ts.map +1 -0
  877. package/dist/tools/xppKnowledge.js +2815 -0
  878. package/dist/tools/xppKnowledge.js.map +1 -0
  879. package/dist/types/context.d.ts +56 -0
  880. package/dist/types/context.d.ts.map +1 -0
  881. package/dist/types/context.js +5 -0
  882. package/dist/types/context.js.map +1 -0
  883. package/dist/utils/callDedup.d.ts +29 -0
  884. package/dist/utils/callDedup.d.ts.map +1 -0
  885. package/dist/utils/callDedup.js +87 -0
  886. package/dist/utils/callDedup.js.map +1 -0
  887. package/dist/utils/configManager.d.ts +307 -0
  888. package/dist/utils/configManager.d.ts.map +1 -0
  889. package/dist/utils/configManager.js +1169 -0
  890. package/dist/utils/configManager.js.map +1 -0
  891. package/dist/utils/d365XmlNormalizer.d.ts +9 -0
  892. package/dist/utils/d365XmlNormalizer.d.ts.map +1 -0
  893. package/dist/utils/d365XmlNormalizer.js +24 -0
  894. package/dist/utils/d365XmlNormalizer.js.map +1 -0
  895. package/dist/utils/edtExtensionValidator.d.ts +110 -0
  896. package/dist/utils/edtExtensionValidator.d.ts.map +1 -0
  897. package/dist/utils/edtExtensionValidator.js +367 -0
  898. package/dist/utils/edtExtensionValidator.js.map +1 -0
  899. package/dist/utils/eolUtils.d.ts +15 -0
  900. package/dist/utils/eolUtils.d.ts.map +1 -0
  901. package/dist/utils/eolUtils.js +21 -0
  902. package/dist/utils/eolUtils.js.map +1 -0
  903. package/dist/utils/extractManifest.d.ts +34 -0
  904. package/dist/utils/extractManifest.d.ts.map +1 -0
  905. package/dist/utils/extractManifest.js +43 -0
  906. package/dist/utils/extractManifest.js.map +1 -0
  907. package/dist/utils/fieldControlTypes.d.ts +44 -0
  908. package/dist/utils/fieldControlTypes.d.ts.map +1 -0
  909. package/dist/utils/fieldControlTypes.js +97 -0
  910. package/dist/utils/fieldControlTypes.js.map +1 -0
  911. package/dist/utils/formCloner.d.ts +77 -0
  912. package/dist/utils/formCloner.d.ts.map +1 -0
  913. package/dist/utils/formCloner.js +364 -0
  914. package/dist/utils/formCloner.js.map +1 -0
  915. package/dist/utils/formControlExpander.d.ts +68 -0
  916. package/dist/utils/formControlExpander.d.ts.map +1 -0
  917. package/dist/utils/formControlExpander.js +207 -0
  918. package/dist/utils/formControlExpander.js.map +1 -0
  919. package/dist/utils/formControlRepair.d.ts +81 -0
  920. package/dist/utils/formControlRepair.d.ts.map +1 -0
  921. package/dist/utils/formControlRepair.js +203 -0
  922. package/dist/utils/formControlRepair.js.map +1 -0
  923. package/dist/utils/formExtensionShapeValidator.d.ts +44 -0
  924. package/dist/utils/formExtensionShapeValidator.d.ts.map +1 -0
  925. package/dist/utils/formExtensionShapeValidator.js +99 -0
  926. package/dist/utils/formExtensionShapeValidator.js.map +1 -0
  927. package/dist/utils/formPatternTemplates.d.ts +77 -0
  928. package/dist/utils/formPatternTemplates.d.ts.map +1 -0
  929. package/dist/utils/formPatternTemplates.js +1917 -0
  930. package/dist/utils/formPatternTemplates.js.map +1 -0
  931. package/dist/utils/fsExtensionScanner.d.ts +76 -0
  932. package/dist/utils/fsExtensionScanner.d.ts.map +1 -0
  933. package/dist/utils/fsExtensionScanner.js +271 -0
  934. package/dist/utils/fsExtensionScanner.js.map +1 -0
  935. package/dist/utils/fuzzyMatching.d.ts +41 -0
  936. package/dist/utils/fuzzyMatching.d.ts.map +1 -0
  937. package/dist/utils/fuzzyMatching.js +186 -0
  938. package/dist/utils/fuzzyMatching.js.map +1 -0
  939. package/dist/utils/indexStaleness.d.ts +30 -0
  940. package/dist/utils/indexStaleness.d.ts.map +1 -0
  941. package/dist/utils/indexStaleness.js +104 -0
  942. package/dist/utils/indexStaleness.js.map +1 -0
  943. package/dist/utils/loadEnv.d.ts +32 -0
  944. package/dist/utils/loadEnv.d.ts.map +1 -0
  945. package/dist/utils/loadEnv.js +83 -0
  946. package/dist/utils/loadEnv.js.map +1 -0
  947. package/dist/utils/logger.d.ts +4 -0
  948. package/dist/utils/logger.d.ts.map +1 -0
  949. package/dist/utils/logger.js +11 -0
  950. package/dist/utils/logger.js.map +1 -0
  951. package/dist/utils/metadataResolver.d.ts +155 -0
  952. package/dist/utils/metadataResolver.d.ts.map +1 -0
  953. package/dist/utils/metadataResolver.js +277 -0
  954. package/dist/utils/metadataResolver.js.map +1 -0
  955. package/dist/utils/modelClassifier.d.ts +180 -0
  956. package/dist/utils/modelClassifier.d.ts.map +1 -0
  957. package/dist/utils/modelClassifier.js +373 -0
  958. package/dist/utils/modelClassifier.js.map +1 -0
  959. package/dist/utils/operationLocks.d.ts +26 -0
  960. package/dist/utils/operationLocks.d.ts.map +1 -0
  961. package/dist/utils/operationLocks.js +182 -0
  962. package/dist/utils/operationLocks.js.map +1 -0
  963. package/dist/utils/packageResolver.d.ts +52 -0
  964. package/dist/utils/packageResolver.d.ts.map +1 -0
  965. package/dist/utils/packageResolver.js +174 -0
  966. package/dist/utils/packageResolver.js.map +1 -0
  967. package/dist/utils/pathContainment.d.ts +60 -0
  968. package/dist/utils/pathContainment.d.ts.map +1 -0
  969. package/dist/utils/pathContainment.js +270 -0
  970. package/dist/utils/pathContainment.js.map +1 -0
  971. package/dist/utils/projectUtils.d.ts +14 -0
  972. package/dist/utils/projectUtils.d.ts.map +1 -0
  973. package/dist/utils/projectUtils.js +48 -0
  974. package/dist/utils/projectUtils.js.map +1 -0
  975. package/dist/utils/provenanceStore.d.ts +54 -0
  976. package/dist/utils/provenanceStore.d.ts.map +1 -0
  977. package/dist/utils/provenanceStore.js +203 -0
  978. package/dist/utils/provenanceStore.js.map +1 -0
  979. package/dist/utils/richContext.d.ts +45 -0
  980. package/dist/utils/richContext.d.ts.map +1 -0
  981. package/dist/utils/richContext.js +316 -0
  982. package/dist/utils/richContext.js.map +1 -0
  983. package/dist/utils/smartXmlBuilder.d.ts +187 -0
  984. package/dist/utils/smartXmlBuilder.d.ts.map +1 -0
  985. package/dist/utils/smartXmlBuilder.js +504 -0
  986. package/dist/utils/smartXmlBuilder.js.map +1 -0
  987. package/dist/utils/stdioSessionInfo.d.ts +53 -0
  988. package/dist/utils/stdioSessionInfo.d.ts.map +1 -0
  989. package/dist/utils/stdioSessionInfo.js +47 -0
  990. package/dist/utils/stdioSessionInfo.js.map +1 -0
  991. package/dist/utils/suggestionEngine.d.ts +20 -0
  992. package/dist/utils/suggestionEngine.d.ts.map +1 -0
  993. package/dist/utils/suggestionEngine.js +145 -0
  994. package/dist/utils/suggestionEngine.js.map +1 -0
  995. package/dist/utils/symbolLookup.d.ts +60 -0
  996. package/dist/utils/symbolLookup.d.ts.map +1 -0
  997. package/dist/utils/symbolLookup.js +0 -0
  998. package/dist/utils/symbolLookup.js.map +1 -0
  999. package/dist/utils/terminalUi.d.ts +85 -0
  1000. package/dist/utils/terminalUi.d.ts.map +1 -0
  1001. package/dist/utils/terminalUi.js +182 -0
  1002. package/dist/utils/terminalUi.js.map +1 -0
  1003. package/dist/utils/toolMetrics.d.ts +32 -0
  1004. package/dist/utils/toolMetrics.d.ts.map +1 -0
  1005. package/dist/utils/toolMetrics.js +89 -0
  1006. package/dist/utils/toolMetrics.js.map +1 -0
  1007. package/dist/utils/toolProgressMessage.d.ts +8 -0
  1008. package/dist/utils/toolProgressMessage.d.ts.map +1 -0
  1009. package/dist/utils/toolProgressMessage.js +182 -0
  1010. package/dist/utils/toolProgressMessage.js.map +1 -0
  1011. package/dist/utils/workspaceDetector.d.ts +51 -0
  1012. package/dist/utils/workspaceDetector.d.ts.map +1 -0
  1013. package/dist/utils/workspaceDetector.js +328 -0
  1014. package/dist/utils/workspaceDetector.js.map +1 -0
  1015. package/dist/utils/xppConfigProvider.d.ts +48 -0
  1016. package/dist/utils/xppConfigProvider.d.ts.map +1 -0
  1017. package/dist/utils/xppConfigProvider.js +121 -0
  1018. package/dist/utils/xppConfigProvider.js.map +1 -0
  1019. package/dist/utils/xppDocGen.d.ts +39 -0
  1020. package/dist/utils/xppDocGen.d.ts.map +1 -0
  1021. package/dist/utils/xppDocGen.js +518 -0
  1022. package/dist/utils/xppDocGen.js.map +1 -0
  1023. package/dist/utils/xppFormat.d.ts +21 -0
  1024. package/dist/utils/xppFormat.d.ts.map +1 -0
  1025. package/dist/utils/xppFormat.js +102 -0
  1026. package/dist/utils/xppFormat.js.map +1 -0
  1027. package/dist/utils/xppSelectLint.d.ts +18 -0
  1028. package/dist/utils/xppSelectLint.d.ts.map +1 -0
  1029. package/dist/utils/xppSelectLint.js +80 -0
  1030. package/dist/utils/xppSelectLint.js.map +1 -0
  1031. package/dist/validation/formPatternValidator.d.ts +53 -0
  1032. package/dist/validation/formPatternValidator.d.ts.map +1 -0
  1033. package/dist/validation/formPatternValidator.js +454 -0
  1034. package/dist/validation/formPatternValidator.js.map +1 -0
  1035. package/dist/workspace/contextRanker.d.ts +58 -0
  1036. package/dist/workspace/contextRanker.d.ts.map +1 -0
  1037. package/dist/workspace/contextRanker.js +216 -0
  1038. package/dist/workspace/contextRanker.js.map +1 -0
  1039. package/dist/workspace/contextSnapshot.d.ts +68 -0
  1040. package/dist/workspace/contextSnapshot.d.ts.map +1 -0
  1041. package/dist/workspace/contextSnapshot.js +193 -0
  1042. package/dist/workspace/contextSnapshot.js.map +1 -0
  1043. package/dist/workspace/hybridSearch.d.ts +39 -0
  1044. package/dist/workspace/hybridSearch.d.ts.map +1 -0
  1045. package/dist/workspace/hybridSearch.js +96 -0
  1046. package/dist/workspace/hybridSearch.js.map +1 -0
  1047. package/dist/workspace/workspaceScanner.d.ts +95 -0
  1048. package/dist/workspace/workspaceScanner.d.ts.map +1 -0
  1049. package/dist/workspace/workspaceScanner.js +247 -0
  1050. package/dist/workspace/workspaceScanner.js.map +1 -0
  1051. package/dist/workspace/workspaceUtils.d.ts +23 -0
  1052. package/dist/workspace/workspaceUtils.d.ts.map +1 -0
  1053. package/dist/workspace/workspaceUtils.js +77 -0
  1054. package/dist/workspace/workspaceUtils.js.map +1 -0
  1055. package/package.json +94 -0
@@ -0,0 +1,4185 @@
1
+ /**
2
+ * D365FO File Creator Tool
3
+ * Creates physical XML files in the AOT package structure
4
+ */
5
+ import * as fs from 'fs/promises';
6
+ import * as path from 'path';
7
+ import { z } from 'zod';
8
+ import { Parser, Builder } from 'xml2js';
9
+ import { getConfigManager, fallbackPackagePath } from '../utils/configManager.js';
10
+ import { registerCustomModel, resolveObjectPrefix, applyObjectPrefix, getObjectSuffix, applyObjectSuffix, getExtensionNamingStyle } from '../utils/modelClassifier.js';
11
+ import { PackageResolver } from '../utils/packageResolver.js';
12
+ import { ensureXppDocComment, ensureBlankLineBeforeClosingBrace } from '../utils/xppDocGen.js';
13
+ import { reindentXppSource } from '../utils/xppFormat.js';
14
+ import { decodeXmlEntitiesFromXppSource } from './modifyD365File.js';
15
+ import { bridgeValidateAfterWrite, canBridgeCreate, bridgeCreateObject, bridgeCreateSmartTable, bridgeRefreshProvider } from '../bridge/index.js';
16
+ import { enforceGrounding } from '../utils/provenanceStore.js';
17
+ import { gateOnFormPatternErrors, isFormPatternEnforceEnabled } from './validateFormPattern.js';
18
+ import { validateFormExtensionControlShape, buildFormExtensionShapeError } from '../utils/formExtensionShapeValidator.js';
19
+ import { FormPatternTemplates } from '../utils/formPatternTemplates.js';
20
+ import { gateOnReferenceErrors } from './resolveReferences.js';
21
+ import { normalizeD365Xml } from '../utils/d365XmlNormalizer.js';
22
+ import { buildAxSecurityPrivilegeXml } from './securityPrivilegeXml.js';
23
+ import { buildAxDataEntityXml } from './dataEntityXml.js';
24
+ import { resolveEdtBaseType, resolveEdtEnumType, heuristicEdtBaseType, isEnumName, bridgeEdtBaseType } from './generateSmartTable.js';
25
+ import { buildAxQueryXml, buildAxViewXml } from './queryViewXml.js';
26
+ import { buildAxMapXml } from './mapXml.js';
27
+ /**
28
+ * Per-project-file mutex to serialise concurrent addToProject calls.
29
+ * Key = normalised absolute project path, Value = tail of the promise chain.
30
+ * Prevents race conditions when the AI calls create_d365fo_file in parallel
31
+ * for multiple objects that share the same .rnrproj file.
32
+ */
33
+ const projectFileLocks = new Map();
34
+ async function withProjectFileLock(projectPath, fn) {
35
+ const key = path.resolve(projectPath).toLowerCase();
36
+ const prev = projectFileLocks.get(key) ?? Promise.resolve();
37
+ let resolve;
38
+ const next = new Promise(r => { resolve = r; });
39
+ projectFileLocks.set(key, next);
40
+ try {
41
+ await prev;
42
+ return await fn();
43
+ }
44
+ finally {
45
+ resolve();
46
+ if (projectFileLocks.get(key) === next) {
47
+ projectFileLocks.delete(key);
48
+ }
49
+ }
50
+ }
51
+ const CreateD365FileArgsSchema = z.object({
52
+ objectType: z
53
+ .enum([
54
+ 'class', 'class-extension', 'table', 'enum', 'form', 'query', 'view', 'data-entity', 'report',
55
+ 'edt', 'edt-extension',
56
+ 'table-extension', 'form-extension', 'data-entity-extension', 'enum-extension',
57
+ 'menu-item-display', 'menu-item-action', 'menu-item-output',
58
+ 'menu-item-display-extension', 'menu-item-action-extension', 'menu-item-output-extension',
59
+ 'menu', 'menu-extension',
60
+ 'security-privilege', 'security-duty', 'security-role',
61
+ 'security-duty-extension', 'security-role-extension',
62
+ 'business-event', 'tile', 'kpi', 'map',
63
+ ])
64
+ .describe('Type of D365FO object to create'),
65
+ objectName: z
66
+ .string()
67
+ .describe('Name of the object (e.g., MyHelperClass, MyCustomTable)'),
68
+ modelName: z
69
+ .string()
70
+ .optional()
71
+ .describe('Model name (e.g., ContosoExtensions). Auto-detected from mcp.json if omitted.'),
72
+ packageName: z
73
+ .string()
74
+ .optional()
75
+ .describe('Package name (e.g., CustomExtensions, ApplicationSuite). Auto-resolved from model name if omitted.'),
76
+ packagePath: z
77
+ .string()
78
+ .optional()
79
+ .describe('Base package path (default: auto-detected from .mcp.json or well-known locations: C:\\, J:\\, K:\\AosService\\PackagesLocalDirectory)'),
80
+ sourceCode: z
81
+ .string()
82
+ .optional()
83
+ .describe('X++ source code for the object (class declaration, methods, etc.)'),
84
+ properties: z
85
+ .record(z.string(), z.any())
86
+ .optional()
87
+ .describe('Additional properties for the object (extends, implements, etc.)'),
88
+ addToProject: z
89
+ .boolean()
90
+ .optional()
91
+ .default(true)
92
+ .describe('Whether to automatically add file to Visual Studio project (default: true — always pass true unless explicitly told not to)'),
93
+ projectPath: z
94
+ .string()
95
+ .optional()
96
+ .describe('Path to .rnrproj file. Required for addToProject to work. If not specified, auto-detected from .mcp.json or workspace context.'),
97
+ solutionPath: z
98
+ .string()
99
+ .optional()
100
+ .describe('Path to active VS solution directory. Used to find .rnrproj when projectPath is not given.'),
101
+ xmlContent: z
102
+ .string()
103
+ .optional()
104
+ .describe('Custom XML content to write verbatim instead of generating a template. ' +
105
+ 'Use this in hybrid setups: call generate / generate on Azure ' +
106
+ 'to get AI-driven XML, then pass that XML here on the local Windows VM to write the file ' +
107
+ 'and add it to the VS2022 project.'),
108
+ overwrite: z
109
+ .boolean()
110
+ .optional()
111
+ .default(false)
112
+ .describe('Allow overwriting an existing file. Use together with xmlContent when you need to completely ' +
113
+ 'rewrite an object (e.g. table with corrupted field names). Default: false (returns error if file already exists).'),
114
+ groundingToken: z
115
+ .string()
116
+ .optional()
117
+ .describe('Provenance token returned by prepare(mode="change"). Proves the change was grounded in the indexed codebase. ' +
118
+ 'Required for *-extension objectTypes when GROUNDING_ENFORCE=true on the server.'),
119
+ });
120
+ /**
121
+ * Project File Finder
122
+ * Finds .rnrproj files in solution directory or specific paths
123
+ */
124
+ export class ProjectFileFinder {
125
+ /**
126
+ * Find .rnrproj file in solution directory
127
+ * Recursively searches for .rnrproj files matching the model name (up to 3 levels deep)
128
+ */
129
+ static async findProjectInSolution(solutionPath, modelName) {
130
+ return ProjectFileFinder.findRecursive(solutionPath, modelName, 0, 3);
131
+ }
132
+ static async findRecursive(dir, modelName, currentDepth, maxDepth) {
133
+ if (currentDepth > maxDepth)
134
+ return null;
135
+ try {
136
+ await fs.access(dir);
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ let files;
142
+ try {
143
+ files = await fs.readdir(dir);
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ // Check .rnrproj files at this level first
149
+ const projectFiles = files.filter(file => file.endsWith('.rnrproj') &&
150
+ (file.includes(modelName) || file === `${modelName}.rnrproj`));
151
+ if (projectFiles.length > 0) {
152
+ return path.join(dir, projectFiles[0]);
153
+ }
154
+ // Recurse into subdirectories
155
+ for (const file of files) {
156
+ const fullPath = path.join(dir, file);
157
+ try {
158
+ const stat = await fs.stat(fullPath);
159
+ if (stat.isDirectory()) {
160
+ const found = await ProjectFileFinder.findRecursive(fullPath, modelName, currentDepth + 1, maxDepth);
161
+ if (found)
162
+ return found;
163
+ }
164
+ }
165
+ catch {
166
+ continue;
167
+ }
168
+ }
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * Map a D365FO base type name to the XML i:type attribute used in <AxTableField>.
174
+ * If the explicit fieldType is not a known primitive, fall back to name-based heuristics
175
+ * using edtName (same heuristics as SmartXmlBuilder.getAxTableFieldType).
176
+ */
177
+ function fieldTypeToAxType(fieldType, edtName) {
178
+ const typeMap = {
179
+ String: 'AxTableFieldString',
180
+ Integer: 'AxTableFieldInt',
181
+ Int64: 'AxTableFieldInt64',
182
+ Real: 'AxTableFieldReal',
183
+ Date: 'AxTableFieldDate',
184
+ DateTime: 'AxTableFieldUtcDateTime',
185
+ UtcDateTime: 'AxTableFieldUtcDateTime',
186
+ Enum: 'AxTableFieldEnum',
187
+ GUID: 'AxTableFieldGuid',
188
+ Guid: 'AxTableFieldGuid',
189
+ Container: 'AxTableFieldContainer',
190
+ };
191
+ const explicit = typeMap[fieldType];
192
+ if (explicit)
193
+ return explicit;
194
+ // Fall back to EDT name heuristics (mirrors SmartXmlBuilder.getAxTableFieldType)
195
+ const hint = edtName || fieldType;
196
+ if (hint) {
197
+ const e = hint.toLowerCase();
198
+ if (e === 'recid' || e.endsWith('recid') || e.includes('refrecid'))
199
+ return 'AxTableFieldInt64';
200
+ if (e.includes('utcdatetime') || (e.includes('datetime') && !e.includes('transdate')))
201
+ return 'AxTableFieldUtcDateTime';
202
+ if (e.includes('date') && !e.includes('time') && !e.includes('update'))
203
+ return 'AxTableFieldDate';
204
+ if (e.includes('amount') || e.includes('mst') || e.includes('price') || e.includes('qty')
205
+ || e.includes('percent') || e === 'real')
206
+ return 'AxTableFieldReal';
207
+ if (e === 'noyesid' || e.endsWith('noyesid') || e === 'noyes')
208
+ return 'AxTableFieldEnum';
209
+ if ((e.endsWith('int') || e.includes('count') || e.includes('level'))
210
+ && !e.includes('account') && !e.includes('name'))
211
+ return 'AxTableFieldInt';
212
+ }
213
+ return 'AxTableFieldString';
214
+ }
215
+ /**
216
+ * Normalize the flexible field specs accepted by the tool / XML generators
217
+ * (`{ name, edt?, type?, fieldType?, extendedDataType?, enumType?, mandatory?, label? }`)
218
+ * into the key shape the C# bridge's WriteFieldParam actually deserializes.
219
+ *
220
+ * The bridge only reads JSON keys `type` and `edt` (`[JsonPropertyName]`), not
221
+ * `fieldType`/`extendedDataType` — accept either input spelling and always emit
222
+ * the bridge's keys. `type` may arrive as a base-type keyword ("Integer") or a
223
+ * full i:type ("AxTableFieldInt"); the latter is stripped back to the keyword
224
+ * the bridge's CreateTableField switch understands.
225
+ */
226
+ export function normalizeFieldSpecsForBridge(fields) {
227
+ return fields.map((f) => {
228
+ let fieldType = f.type ?? f.fieldType;
229
+ if (typeof fieldType === 'string')
230
+ fieldType = fieldType.replace(/^AxTableField/, '');
231
+ const out = { name: f.name };
232
+ if (fieldType != null && fieldType !== '')
233
+ out.type = fieldType;
234
+ if (f.edt != null)
235
+ out.edt = f.edt;
236
+ else if (f.extendedDataType != null)
237
+ out.edt = f.extendedDataType;
238
+ if (f.enumType != null)
239
+ out.enumType = f.enumType;
240
+ if (f.mandatory != null)
241
+ out.mandatory = f.mandatory;
242
+ if (f.label != null)
243
+ out.label = f.label;
244
+ if (f.stringSize != null)
245
+ out.stringSize = f.stringSize;
246
+ return out;
247
+ });
248
+ }
249
+ /**
250
+ * Normalize the flexible index specs accepted by the tool into the key shape the
251
+ * C# bridge's WriteIndexParam actually deserializes: `{ name, fields: string[],
252
+ * alternateKey?, allowDuplicates? }`.
253
+ *
254
+ * Accepts both the bridge's native `{name, fields}` shape and the documented
255
+ * `modify(operation="add-index")` shape `{indexName, indexFields: [{fieldName}]}`.
256
+ * Unrecognized keys are silently ignored by System.Text.Json, so any unmapped
257
+ * shape produces an index with an empty Name/Fields that xppc rejects at build time.
258
+ */
259
+ export function normalizeIndexSpecsForBridge(indexes) {
260
+ return indexes.map((idx) => {
261
+ const name = idx.name ?? idx.indexName;
262
+ const rawFields = (idx.fields ?? idx.indexFields);
263
+ const fields = Array.isArray(rawFields)
264
+ ? rawFields
265
+ .map((f) => (typeof f === 'string' ? f : f?.fieldName))
266
+ .filter((f) => typeof f === 'string' && f.length > 0)
267
+ : undefined;
268
+ const out = { name };
269
+ if (fields && fields.length > 0)
270
+ out.fields = fields;
271
+ if (idx.alternateKey != null)
272
+ out.alternateKey = idx.alternateKey;
273
+ if (idx.allowDuplicates != null)
274
+ out.allowDuplicates = idx.allowDuplicates;
275
+ return out;
276
+ });
277
+ }
278
+ /**
279
+ * XML Templates for different D365FO object types
280
+ */
281
+ export class XmlTemplateGenerator {
282
+ /**
283
+ * Split X++ class source into the Declaration block (class header + field
284
+ * declarations) and individual method bodies, as required by D365FO XML.
285
+ *
286
+ * D365FO XML structure:
287
+ * <Declaration> = class keyword + field declarations (the outer {} block)
288
+ * <Methods> = one <Method><Name/><Source/></Method> per method body
289
+ *
290
+ * AI generators often emit the entire source (header + methods) as a single
291
+ * string. This helper separates them so the generated XML is correct.
292
+ */
293
+ static splitXppClassSource(fullSource) {
294
+ // Find the '{' that opens the class body
295
+ const firstBrace = fullSource.indexOf('{');
296
+ if (firstBrace === -1)
297
+ return { declaration: fullSource, methods: [] };
298
+ // Walk to the matching '}' that closes the class header block
299
+ let depth = 0;
300
+ let classEndIdx = -1;
301
+ for (let i = firstBrace; i < fullSource.length; i++) {
302
+ if (fullSource[i] === '{')
303
+ depth++;
304
+ else if (fullSource[i] === '}') {
305
+ depth--;
306
+ if (depth === 0) {
307
+ classEndIdx = i;
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ if (classEndIdx === -1)
313
+ return { declaration: fullSource, methods: [] };
314
+ let declaration = fullSource.substring(0, classEndIdx + 1);
315
+ const rest = fullSource.substring(classEndIdx + 1);
316
+ if (!rest.trim()) {
317
+ // Nothing after the class closing brace — methods may be nested inside the class braces instead.
318
+ const innerResult = XmlTemplateGenerator.extractInnerClassMethods(declaration);
319
+ if (innerResult) {
320
+ console.error('[splitXppClassSource] Inner-class methods detected — extracting into ' +
321
+ 'separate <Method> elements (D365FO format).');
322
+ return innerResult;
323
+ }
324
+ // Ensure exactly one blank line before the closing '}' when the body has content.
325
+ const bodyStart = declaration.indexOf('{');
326
+ const bodyContent = declaration.substring(bodyStart + 1, declaration.lastIndexOf('}'));
327
+ if (bodyContent.trim().length > 0) {
328
+ declaration = declaration.replace(/\n+(\s*)}(\s*)$/, '\n\n}');
329
+ }
330
+ return { declaration, methods: [] };
331
+ }
332
+ // D365FO requires member-variable declarations inside <Declaration>, so rescue any that
333
+ // appear outside the class {} but before the first method.
334
+ const nextBraceInRest = rest.indexOf('{');
335
+ if (nextBraceInRest !== -1) {
336
+ const preMethodText = rest.substring(0, nextBraceInRest);
337
+ const varLines = preMethodText
338
+ .split('\n')
339
+ .filter(l => {
340
+ const t = l.trim();
341
+ // A variable declaration ends with ';' and does NOT contain '(' (not a method call/signature)
342
+ return t.endsWith(';') && !t.includes('(');
343
+ });
344
+ if (varLines.length > 0) {
345
+ // Inject the rescued declarations into the class body, just before the closing '}'
346
+ const injected = varLines.map(l => ' ' + l.trim()).join('\n');
347
+ declaration = declaration.replace(/}(\s*)$/, `\n${injected}\n\n}`);
348
+ console.error(`[splitXppClassSource] Rescued ${varLines.length} member variable declaration(s) ` +
349
+ 'found outside the class {} block — injected into <Declaration>.');
350
+ }
351
+ }
352
+ // Parse each method block from the remaining source
353
+ const methods = [];
354
+ let pos = 0;
355
+ while (pos < rest.length) {
356
+ const nextBrace = rest.indexOf('{', pos);
357
+ if (nextBrace === -1)
358
+ break;
359
+ const sigText = rest.substring(pos, nextBrace);
360
+ // Find the matching '}' for this method body (depth-counting)
361
+ let d = 0;
362
+ let bodyEnd = -1;
363
+ for (let i = nextBrace; i < rest.length; i++) {
364
+ if (rest[i] === '{')
365
+ d++;
366
+ else if (rest[i] === '}') {
367
+ d--;
368
+ if (d === 0) {
369
+ bodyEnd = i;
370
+ break;
371
+ }
372
+ }
373
+ }
374
+ if (bodyEnd === -1)
375
+ break;
376
+ const methodSource = rest.substring(pos, bodyEnd + 1).trim();
377
+ // Extract method name: last identifier before '(' in the signature
378
+ const parenIdx = sigText.lastIndexOf('(');
379
+ const nameMatch = parenIdx !== -1 ? sigText.substring(0, parenIdx).match(/(\w+)\s*$/) : null;
380
+ const methodName = nameMatch ? nameMatch[1] : `method${methods.length + 1}`;
381
+ methods.push({ name: methodName, source: methodSource });
382
+ pos = bodyEnd + 1;
383
+ }
384
+ // Fallback: if the source had no methods after the class body, they may be
385
+ // nested at depth-1 inside the class body instead — extract them there.
386
+ if (methods.length === 0) {
387
+ const innerResult = XmlTemplateGenerator.extractInnerClassMethods(declaration);
388
+ if (innerResult) {
389
+ console.error('[splitXppClassSource] Extracted inner class methods — moving them from ' +
390
+ '<Declaration> into separate <Method> elements (D365FO format).');
391
+ return innerResult;
392
+ }
393
+ }
394
+ return { declaration, methods };
395
+ }
396
+ /**
397
+ * Extract methods that are defined INSIDE the class body (depth-1 inside {}).
398
+ *
399
+ * D365FO XML requires each method as a separate <Method><Source/></Method> element.
400
+ * When AI generates a class with methods inside the class braces, all code ends up
401
+ * in <Declaration> with no blank-line separation between methods.
402
+ *
403
+ * This helper detects that pattern and returns the correct split:
404
+ * declaration = class header + member variable declarations only
405
+ * methods = each method body as a separate entry
406
+ *
407
+ * Returns null when no inner methods are found (i.e. the class body is just fields).
408
+ */
409
+ static extractInnerClassMethods(classDeclaration) {
410
+ const classOpenIdx = classDeclaration.indexOf('{');
411
+ const classCloseIdx = classDeclaration.lastIndexOf('}');
412
+ if (classOpenIdx === -1 || classCloseIdx <= classOpenIdx)
413
+ return null;
414
+ const classBody = classDeclaration.substring(classOpenIdx + 1, classCloseIdx);
415
+ const methods = [];
416
+ const memberVarLines = [];
417
+ let pos = 0;
418
+ while (pos < classBody.length) {
419
+ const nextBrace = classBody.indexOf('{', pos);
420
+ if (nextBrace === -1) {
421
+ // No more braces — collect any trailing member-variable declarations
422
+ for (const line of classBody.substring(pos).split('\n')) {
423
+ const t = line.trim();
424
+ if (t.length > 0 && t.endsWith(';') && !t.includes('(') &&
425
+ !t.startsWith('//') && !t.startsWith('*')) {
426
+ memberVarLines.push(t);
427
+ }
428
+ }
429
+ break;
430
+ }
431
+ const sigText = classBody.substring(pos, nextBrace);
432
+ // Find the matching '}' for this block
433
+ let depth = 0;
434
+ let bodyEnd = -1;
435
+ for (let i = nextBrace; i < classBody.length; i++) {
436
+ if (classBody[i] === '{')
437
+ depth++;
438
+ else if (classBody[i] === '}') {
439
+ depth--;
440
+ if (depth === 0) {
441
+ bodyEnd = i;
442
+ break;
443
+ }
444
+ }
445
+ }
446
+ if (bodyEnd === -1)
447
+ break;
448
+ const parenIdx = sigText.lastIndexOf('(');
449
+ if (parenIdx !== -1) {
450
+ // The block is a method (sigText contains a '(' — it's a parameter list).
451
+ // Collect member-variable lines that appeared before the method signature.
452
+ for (const line of sigText.split('\n')) {
453
+ const t = line.trim();
454
+ if (t.length > 0 && t.endsWith(';') && !t.includes('(') &&
455
+ !t.startsWith('//') && !t.startsWith('*') && !t.startsWith('[')) {
456
+ memberVarLines.push(t);
457
+ }
458
+ }
459
+ // Find where the actual method signature starts within sigText:
460
+ // walk backward from the last '(' to include attribute annotations ('[…]').
461
+ const beforeLastParen = sigText.substring(0, parenIdx);
462
+ const lastNewlineBeforeLastParen = beforeLastParen.lastIndexOf('\n');
463
+ let methodStartInSig = lastNewlineBeforeLastParen !== -1
464
+ ? lastNewlineBeforeLastParen + 1
465
+ : 0;
466
+ // Include any leading [Attribute] and doc-comment (///) lines that belong to this method.
467
+ // Walking BACKWARDS through the lines that appear above the method signature:
468
+ // we stop as soon as we hit a line that is neither empty, nor an attribute, nor a comment.
469
+ const sigBeforeMethod = sigText.substring(0, methodStartInSig);
470
+ const sigBeforeLines = sigBeforeMethod.split('\n').reverse();
471
+ let droppedChars = 0;
472
+ for (const line of sigBeforeLines) {
473
+ const t = line.trim();
474
+ if (t.length === 0 || t.startsWith('[') || t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) {
475
+ droppedChars += line.length + 1; // +1 for '\n'
476
+ }
477
+ else {
478
+ break;
479
+ }
480
+ }
481
+ methodStartInSig = Math.max(0, methodStartInSig - droppedChars);
482
+ const methodSource = classBody
483
+ .substring(pos + methodStartInSig, bodyEnd + 1)
484
+ .trim();
485
+ const nameMatch = sigText.substring(0, parenIdx).match(/(\w+)\s*$/);
486
+ const methodName = nameMatch ? nameMatch[1] : `method${methods.length + 1}`;
487
+ methods.push({ name: methodName, source: methodSource });
488
+ }
489
+ else {
490
+ // Not a method (no '(' in sigText) — collect member-variable declarations
491
+ for (const line of sigText.split('\n')) {
492
+ const t = line.trim();
493
+ if (t.length > 0 && t.endsWith(';') && !t.includes('(') &&
494
+ !t.startsWith('//') && !t.startsWith('*')) {
495
+ memberVarLines.push(t);
496
+ }
497
+ }
498
+ // Skip the block (e.g. object initialiser)
499
+ }
500
+ pos = bodyEnd + 1;
501
+ }
502
+ if (methods.length === 0)
503
+ return null;
504
+ // Rebuild the declaration as: class header + member variable declarations only
505
+ const classHeader = classDeclaration.substring(0, classOpenIdx + 1);
506
+ const memberVarsXpp = memberVarLines.length > 0
507
+ ? '\n' + memberVarLines.map(v => ' ' + v).join('\n') + '\n\n'
508
+ : '\n';
509
+ return {
510
+ declaration: classHeader + memberVarsXpp + '}',
511
+ methods,
512
+ };
513
+ }
514
+ /**
515
+ * If `declaration`'s own `class`/`interface` header names something other than
516
+ * `className` (e.g. the object is being created as "ContosoFoo" but the caller's
517
+ * X++ still says `class Foo`), rename every self-reference to that stale name —
518
+ * in the header AND in every method body (constructor calls, return types,
519
+ * etc.) — to `className`.
520
+ *
521
+ * Left unfixed, the AOT object's `<Name>` (which the create path always sets to
522
+ * the resolved `className`, independent of whatever the caller typed in
523
+ * `sourceCode`) does not match its own X++ class keyword — a hard xppc build
524
+ * error ("class must be named the same as the object it is contained in"),
525
+ * confirmed by corpus evidence: the caller passed an already-correct,
526
+ * fully-resolved objectName ("ContosoXyzNoteFormatter") — so the existing
527
+ * objectName-vs-finalObjectName prefix-mismatch guard below never fired — while
528
+ * sourceCode's `class XyzNoteFormatter` used the bare, unprefixed name
529
+ * (eval/corpus/runs/2026-07-06T16__L1-class-basic__73707ff.json).
530
+ */
531
+ static normalizeSelfReferenceName(className, declaration, methods) {
532
+ const declaredName = declaration.match(/\b(?:class|interface)\s+(\w+)/)?.[1];
533
+ if (!declaredName || declaredName === className)
534
+ return { declaration, methods };
535
+ const re = new RegExp(`\\b${declaredName}\\b`, 'g');
536
+ return {
537
+ declaration: declaration.replace(re, className),
538
+ methods: methods.map(m => ({
539
+ ...m,
540
+ source: m.source !== undefined ? m.source.replace(re, className) : m.source,
541
+ })),
542
+ };
543
+ }
544
+ /**
545
+ * Parse X++ sourceCode into declaration + methods for the C# bridge.
546
+ *
547
+ * Used by the bridge-first creation path in create_d365fo_file — the C# side
548
+ * expects declaration (class header + member vars) and an array of method
549
+ * objects {name, source} which it sets on the AxClass via IMetadataProvider.
550
+ *
551
+ * Delegates to splitXppClassSource after decoding any XML entities.
552
+ *
553
+ * `className` is the resolved AOT object name the bridge will create this
554
+ * class under (`finalObjectName`) — passing it lets self-references in the
555
+ * caller's sourceCode that don't match get corrected (normalizeSelfReferenceName).
556
+ */
557
+ static parseSourceForBridge(sourceCode, className) {
558
+ // Same entity-decoding as generateAxClassXml to handle AI-generated &lt; etc.
559
+ const cleaned = decodeXmlEntitiesFromXppSource(sourceCode);
560
+ const split = XmlTemplateGenerator.splitXppClassSource(cleaned);
561
+ const result = className
562
+ ? XmlTemplateGenerator.normalizeSelfReferenceName(className, split.declaration, split.methods)
563
+ : split;
564
+ return {
565
+ declaration: result.declaration,
566
+ // The bridge stores each method's source verbatim (no reformatting on its
567
+ // side) — re-derive consistent indentation here so this CREATE path gets
568
+ // the same fix as bridgeAddMethod's MODIFY path.
569
+ methods: result.methods.map(m => ({
570
+ ...m,
571
+ source: m.source !== undefined ? reindentXppSource(m.source) : m.source,
572
+ })),
573
+ };
574
+ }
575
+ /**
576
+ * Generate AxClass XML structure
577
+ */
578
+ static generateAxClassXml(className, sourceCode, properties) {
579
+ // Decode XML entities that AI models may introduce when copying from SSRS report
580
+ // entity-encoded <Text> blocks (e.g. &lt;summary&gt; → <summary>).
581
+ const rawSource = decodeXmlEntitiesFromXppSource(sourceCode || `public class ${className}\n{\n}`);
582
+ // Split full X++ source into Declaration (class header + fields) and Methods.
583
+ // D365FO XML requires member variable declarations in <Declaration> and
584
+ // each method body as a separate <Method> element under <Methods>.
585
+ const rawSplit = XmlTemplateGenerator.splitXppClassSource(rawSource);
586
+ // Correct any stale self-reference (caller's class/interface name doesn't match
587
+ // the resolved `className`) so the emitted <Name> and X++ class keyword agree.
588
+ const { declaration, methods } = XmlTemplateGenerator.normalizeSelfReferenceName(className, rawSplit.declaration, rawSplit.methods);
589
+ const extendsAttr = properties?.extends
590
+ ? `\t<Extends>${properties.extends}</Extends>\n`
591
+ : '';
592
+ const implementsAttr = properties?.implements
593
+ ? `\t<Implements>${properties.implements}</Implements>\n`
594
+ : '';
595
+ const methodsXml = methods.length === 0
596
+ ? '\t\t<Methods />\n'
597
+ : `\t\t<Methods>\n${methods
598
+ .map(m => `\t\t\t<Method>\n\t\t\t\t<Name>${m.name}</Name>\n\t\t\t\t<Source><![CDATA[\n${reindentXppSource(ensureXppDocComment(m.source))}\n\n]]></Source>\n\t\t\t</Method>`)
599
+ .join('\n\n')}\n\t\t</Methods>\n`;
600
+ return `<?xml version="1.0" encoding="utf-8"?>
601
+ <AxClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
602
+ \t<Name>${className}</Name>
603
+ ${extendsAttr}${implementsAttr}\t<SourceCode>
604
+ \t\t<Declaration><![CDATA[
605
+ ${ensureBlankLineBeforeClosingBrace(ensureXppDocComment(declaration))}
606
+ ]]></Declaration>
607
+ ${methodsXml}\t</SourceCode>
608
+ </AxClass>
609
+ `;
610
+ }
611
+ /**
612
+ * Generate AxClass XML structure for a Chain of Command (class-extension).
613
+ * The XML format is identical to a regular AxClass — the distinction is purely
614
+ * in the X++ source code ([ExtensionOf(classStr(...))] + final modifier).
615
+ *
616
+ * properties.baseClass — name of the class being extended (required)
617
+ * properties.modelInfix — naming infix, e.g. "ContosoExt" → BaseClass_ContosoExt_Extension
618
+ */
619
+ static generateAxClassExtensionXml(extensionName, sourceCode, properties) {
620
+ const baseClass = properties?.baseClass || extensionName.replace(/_[^_]+_Extension$/, '');
621
+ const defaultSource = sourceCode ||
622
+ `[ExtensionOf(classStr(${baseClass}))]\nfinal class ${extensionName}\n{\n // ⚠️ ALWAYS call next <methodName>() — verify exact signature with:\n // get_method(include="signature", "${baseClass}", "methodName")\n //\n // Template for wrapping a method:\n // public ReturnType methodName(ParamType _param)\n // {\n // ReturnType result = next methodName(_param);\n // return result;\n // }\n}`;
623
+ return XmlTemplateGenerator.generateAxClassXml(extensionName, defaultSource, { ...properties });
624
+ }
625
+ /**
626
+ * Generate AxTable XML structure (based on real D365FO table structure)
627
+ */
628
+ static generateAxTableXml(tableName, properties) {
629
+ const label = properties?.label || tableName;
630
+ const tableGroup = properties?.tableGroup || 'Main';
631
+ const tableType = properties?.tableType || '';
632
+ const titleField1 = properties?.titleField1 || '';
633
+ const titleField2 = properties?.titleField2 || '';
634
+ const configKey = properties?.configurationKey || '';
635
+ const primaryIndex = properties?.primaryIndex || '';
636
+ const cacheLookup = properties?.cacheLookup || '';
637
+ // Build optional configuration key
638
+ const configKeyXml = configKey
639
+ ? `\t<ConfigurationKey>${configKey}</ConfigurationKey>\n`
640
+ : '';
641
+ // Build optional cache lookup (only if explicitly set)
642
+ const cacheLookupXml = cacheLookup
643
+ ? `\t<CacheLookup>${cacheLookup}</CacheLookup>\n`
644
+ : '';
645
+ // Build optional primary index (NOTE: ClusteredIndex is NOT in real D365FO files)
646
+ const primaryIndexXml = primaryIndex
647
+ ? `\t<PrimaryIndex>${primaryIndex}</PrimaryIndex>\n\t<ReplacementKey>${primaryIndex}</ReplacementKey>\n`
648
+ : '';
649
+ // Build optional TableType (TempDB, InMemory; omit for Regular — it's the default)
650
+ const tableTypeXml = tableType
651
+ ? `\t<TableType>${tableType}</TableType>\n`
652
+ : '';
653
+ // Build <Fields> block from properties.fields array (TableFieldSpec[]).
654
+ // Copilot may pass field definitions via properties.fields or via sourceCode JSON —
655
+ // both paths merge into properties before calling here (see generate()).
656
+ // Field-spec keys are unified with the table-extension path (generateAxTableExtensionXml):
657
+ // accept an explicit AxTableField* i:type (fieldType), a primitive base type (type),
658
+ // or infer AxTableFieldEnum from enumType — and always emit <EnumType> for enum fields.
659
+ const fieldSpecs = Array.isArray(properties?.fields) ? properties.fields : [];
660
+ let fieldsXml;
661
+ if (fieldSpecs.length === 0) {
662
+ fieldsXml = '\t<Fields />\n';
663
+ }
664
+ else {
665
+ fieldsXml = '\t<Fields>\n';
666
+ for (const f of fieldSpecs) {
667
+ // Determine i:type: explicit AxTableField* wins; otherwise derive from the
668
+ // primitive type / enumType / EDT name heuristics. NEVER default to
669
+ // AxTableFieldString blindly when an EDT or enumType is present.
670
+ const iType = f.fieldType
671
+ ?? fieldTypeToAxType(f.type || (f.enumType ? 'Enum' : 'String'), f.edt);
672
+ fieldsXml += `\t\t<AxTableField xmlns=""\n\t\t\ti:type="${iType}">\n`;
673
+ fieldsXml += `\t\t\t<Name>${f.name}</Name>\n`;
674
+ if (f.edt)
675
+ fieldsXml += `\t\t\t<ExtendedDataType>${f.edt}</ExtendedDataType>\n`;
676
+ if (f.label)
677
+ fieldsXml += `\t\t\t<Label>${f.label}</Label>\n`;
678
+ if (f.mandatory)
679
+ fieldsXml += `\t\t\t<Mandatory>Yes</Mandatory>\n`;
680
+ if (f.enumType)
681
+ fieldsXml += `\t\t\t<EnumType>${f.enumType}</EnumType>\n`;
682
+ fieldsXml += `\t\t</AxTableField>\n`;
683
+ }
684
+ fieldsXml += '\t</Fields>\n';
685
+ }
686
+ return `<?xml version="1.0" encoding="utf-8"?>
687
+ <AxTable xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
688
+ \t<Name>${tableName}</Name>
689
+ \t<SourceCode>
690
+ \t\t<Declaration><![CDATA[
691
+ public class ${tableName} extends common
692
+ {
693
+ }
694
+ ]]></Declaration>
695
+ \t\t<Methods />
696
+ \t</SourceCode>
697
+ ${configKeyXml}\t<Label>${label}</Label>
698
+ \t<TableGroup>${tableGroup}</TableGroup>
699
+ ${tableTypeXml}\t<TitleField1>${titleField1}</TitleField1>
700
+ \t<TitleField2>${titleField2}</TitleField2>
701
+ ${cacheLookupXml}${primaryIndexXml}\t<DeleteActions />
702
+ \t<FieldGroups>
703
+ \t\t<AxTableFieldGroup>
704
+ \t\t\t<Name>AutoReport</Name>
705
+ \t\t\t<Fields />
706
+ \t\t</AxTableFieldGroup>
707
+ \t\t<AxTableFieldGroup>
708
+ \t\t\t<Name>AutoLookup</Name>
709
+ \t\t\t<Fields />
710
+ \t\t</AxTableFieldGroup>
711
+ \t\t<AxTableFieldGroup>
712
+ \t\t\t<Name>AutoIdentification</Name>
713
+ \t\t\t<AutoPopulate>Yes</AutoPopulate>
714
+ \t\t\t<Fields />
715
+ \t\t</AxTableFieldGroup>
716
+ \t\t<AxTableFieldGroup>
717
+ \t\t\t<Name>AutoSummary</Name>
718
+ \t\t\t<Fields />
719
+ \t\t</AxTableFieldGroup>
720
+ \t\t<AxTableFieldGroup>
721
+ \t\t\t<Name>AutoBrowse</Name>
722
+ \t\t\t<Fields />
723
+ \t\t</AxTableFieldGroup>
724
+ \t</FieldGroups>
725
+ ${fieldsXml}\t<FullTextIndexes />
726
+ \t<Indexes />
727
+ \t<Mappings />
728
+ \t<Relations />
729
+ \t<StateMachines />
730
+ </AxTable>
731
+ `;
732
+ }
733
+ /**
734
+ * Generate AxEnum XML structure
735
+ */
736
+ static generateAxEnumXml(enumName, properties) {
737
+ const label = properties?.label || enumName;
738
+ // Extensible enums MUST have UseEnumValue=No (xppc hard requirement).
739
+ // Explicit <Value> elements also force UseEnumValue=Yes at compile time,
740
+ // so we suppress them when UseEnumValue=No.
741
+ const useEnumValue = (properties?.isExtensible || properties?.useEnumValue === false)
742
+ ? 'No'
743
+ : (properties?.useEnumValue ? 'Yes' : 'No');
744
+ const suppressExplicitValues = useEnumValue === 'No';
745
+ const configKeyXml = properties?.configurationKey
746
+ ? `\t<ConfigurationKey>${properties.configurationKey}</ConfigurationKey>\n`
747
+ : '';
748
+ // Build <EnumValues> block from properties.enumValues array
749
+ // Each entry: { name: string; value?: number; label?: string; helpText?: string }
750
+ const enumValueSpecs = Array.isArray(properties?.enumValues) ? properties.enumValues : [];
751
+ // D365FO hard limit: max 251 elements (0–250). Warn early — compiler rejects beyond this.
752
+ if (enumValueSpecs.length > 251) {
753
+ throw new Error(`Enum '${enumName}' has ${enumValueSpecs.length} values but D365FO supports a maximum of 251 (0–250). ` +
754
+ `Consider redesigning as a class hierarchy or splitting into multiple enums.`);
755
+ }
756
+ let enumValuesXml;
757
+ if (enumValueSpecs.length === 0) {
758
+ enumValuesXml = '\t<EnumValues />\n';
759
+ }
760
+ else {
761
+ enumValuesXml = '\t<EnumValues>\n';
762
+ let autoValue = 0;
763
+ for (const v of enumValueSpecs) {
764
+ const intValue = v.value ?? autoValue;
765
+ autoValue = intValue + 1;
766
+ enumValuesXml += `\t\t<AxEnumValue>\n`;
767
+ enumValuesXml += `\t\t\t<Name>${v.name}</Name>\n`;
768
+ if (v.label)
769
+ enumValuesXml += `\t\t\t<Label>${v.label}</Label>\n`;
770
+ if (v.helpText)
771
+ enumValuesXml += `\t\t\t<HelpText>${v.helpText}</HelpText>\n`;
772
+ // Omit <Value> when UseEnumValue=No (position-based ordering) or for implicit 0
773
+ if (intValue !== 0 && !suppressExplicitValues)
774
+ enumValuesXml += `\t\t\t<Value>${intValue}</Value>\n`;
775
+ enumValuesXml += `\t\t</AxEnumValue>\n`;
776
+ }
777
+ enumValuesXml += '\t</EnumValues>\n';
778
+ }
779
+ // IsExtensible goes after EnumValues; value is lowercase true/false
780
+ const isExtensibleXml = properties?.isExtensible ? '\t<IsExtensible>true</IsExtensible>\n' : '';
781
+ // Element order matches real D365FO: Name → ConfigurationKey → Label → UseEnumValue → EnumValues → IsExtensible
782
+ return `<?xml version="1.0" encoding="utf-8"?>
783
+ <AxEnum xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
784
+ \t<Name>${enumName}</Name>
785
+ ${configKeyXml}\t<Label>${label}</Label>
786
+ \t<UseEnumValue>${useEnumValue}</UseEnumValue>
787
+ ${enumValuesXml}${isExtensibleXml}</AxEnum>
788
+ `;
789
+ }
790
+ /**
791
+ * Generate AxForm XML for a new form from a pattern template.
792
+ *
793
+ * Delegates to the pattern-compliant {@link FormPatternTemplates} builders so
794
+ * the generated skeleton actually satisfies the form-pattern gate. The old
795
+ * inline skeleton declared a `<Pattern>` over empty `<Controls />`, which the
796
+ * gate rejected as FP003 (required Grid/ActionPane missing) for every pattern
797
+ * — and worse, defaulted to a `DetailsTransaction` pattern even when the
798
+ * caller asked for a `SimpleList` template, guaranteeing a mismatch block.
799
+ *
800
+ * Pattern resolution: callers may express the intent as either `pattern`
801
+ * (the design pattern) or `formTemplate` (the VS template name); both are
802
+ * fuzzy strings normalized to a canonical pattern. When neither is given we
803
+ * default to SimpleList — the most common shape for a new setup table.
804
+ */
805
+ static generateAxFormXml(formName, properties) {
806
+ const rawPattern = properties?.pattern || properties?.formTemplate;
807
+ const pattern = rawPattern
808
+ ? FormPatternTemplates.normalizePattern(String(rawPattern))
809
+ : 'SimpleList';
810
+ return FormPatternTemplates.build(pattern, {
811
+ formName,
812
+ dsName: properties?.dataSource || undefined,
813
+ dsTable: properties?.dataSourceTable || properties?.dataSource || undefined,
814
+ caption: properties?.caption,
815
+ gridFields: Array.isArray(properties?.gridFields) ? properties.gridFields : undefined,
816
+ linesDsName: properties?.linesDataSource,
817
+ linesDsTable: properties?.linesDataSourceTable || properties?.linesDataSource,
818
+ sections: Array.isArray(properties?.sections) ? properties.sections : undefined,
819
+ });
820
+ }
821
+ /**
822
+ * Generate AxQuery XML structure. Delegates to the shared builder so this
823
+ * cannot drift from generateD365Xml.ts's copy — see queryViewXml.ts for the
824
+ * property contract and why `dataSource` matters.
825
+ */
826
+ static generateAxQueryXml(queryName, properties) {
827
+ return buildAxQueryXml(queryName, properties);
828
+ }
829
+ /**
830
+ * Generate AxView XML structure. Delegates to the shared builder so this
831
+ * cannot drift from generateD365Xml.ts's copy — see queryViewXml.ts for the
832
+ * property contract and why `query`/`fields` matter.
833
+ */
834
+ static generateAxViewXml(viewName, properties) {
835
+ return buildAxViewXml(viewName, properties);
836
+ }
837
+ /**
838
+ * Generate AxMap XML structure. Delegates to the shared builder so this
839
+ * cannot drift from generateD365Xml.ts's copy — see mapXml.ts for the
840
+ * property contract.
841
+ */
842
+ static generateAxMapXml(mapName, properties) {
843
+ return buildAxMapXml(mapName, properties);
844
+ }
845
+ /**
846
+ * Generate AxDataEntityView XML structure
847
+ */
848
+ /**
849
+ * Generate AxDataEntityView XML. Delegates to the shared builder so this
850
+ * cannot drift from generateD365Xml.ts's copy (they already had — see
851
+ * dataEntityXml.ts for the property contract and why primaryTable/fields
852
+ * matter).
853
+ */
854
+ static generateAxDataEntityXml(entityName, properties) {
855
+ return buildAxDataEntityXml(entityName, properties);
856
+ }
857
+ /**
858
+ * Generate AxReport XML skeleton.
859
+ *
860
+ * properties:
861
+ * dpClassName - Data Provider class name (default: <ReportName>DP)
862
+ * tmpTableName - TempDB table name (default: <ReportName>Tmp)
863
+ * datasetName - AxReportDataSet name (default: tmpTableName)
864
+ * designName - AxReportDesign name (default: 'Report')
865
+ * caption - Design caption label ref (e.g. '@MyModel:MyLabel')
866
+ * style - Design style template (e.g. 'TableStyleTemplate')
867
+ * aotQuery - AOT query name for DynamicParameter (e.g. 'SalesTable')
868
+ * fields - Array of { name, alias?, dataType?, caption?, disableAutoCreate? } → AxReportDataSetField
869
+ * datasets - Array of { name, dpClassName, tmpTableName, fields?, aotQuery?, contractParams? } for multi-dataset reports
870
+ * contractParams - Array of { name, dataType?, label?, defaultValue? } → contract class parameters (DataMember)
871
+ * rdlContent - Full RDL XML string to embed (auto-generated from fields when omitted)
872
+ *
873
+ * AOT structure generated (mirrors real D365FO reports like ContosoReports_CashOrder_CZ):
874
+ * <AxReport xmlns="Microsoft.Dynamics.AX.Metadata.V2">
875
+ * <DataMethods />
876
+ * <DataSets>
877
+ * <AxReportDataSet xmlns=""> ← one per dataset
878
+ * <Fields>…</Fields>
879
+ * <Parameters> ← 6 AX system params + {DPCLASS}_DynamicParameter
880
+ * </AxReportDataSet>
881
+ * </DataSets>
882
+ * <DefaultParameterGroup> ← 6 AX params + DynamicParameter (with AOTQuery+DataType)
883
+ * <Designs>
884
+ * <AxReportDesign xmlns="" i:type="AxReportPrecisionDesign">
885
+ * <Text><![CDATA[…RDL…]]></Text> ← 2016 schema with DataSources/DataSets/ReportParameters
886
+ * <DisableIndividualTransformation><Name>…</Name></DisableIndividualTransformation>
887
+ * </Designs>
888
+ * </AxReport>
889
+ */
890
+ static generateAxReportXml(reportName, properties) {
891
+ // ── Resolve datasets (multi-dataset array OR single-dataset shorthand) ──
892
+ let datasets;
893
+ if (properties?.datasets && Array.isArray(properties.datasets)) {
894
+ datasets = properties.datasets;
895
+ }
896
+ else {
897
+ const tmpTableName = properties?.tmpTableName || `${reportName}Tmp`;
898
+ const dpClassName = properties?.dpClassName || `${reportName}DP`;
899
+ const datasetName = properties?.datasetName || tmpTableName;
900
+ datasets = [{
901
+ name: datasetName,
902
+ dpClassName,
903
+ tmpTableName,
904
+ fields: properties?.fields,
905
+ aotQuery: properties?.aotQuery,
906
+ contractParams: properties?.contractParams,
907
+ }];
908
+ }
909
+ const designName = properties?.designName || 'Report';
910
+ // ── RDL .NET type mapping ──
911
+ const rdlType = (dt) => {
912
+ switch (dt) {
913
+ case 'System.Double': return 'System.Double';
914
+ case 'System.Int32': return 'System.Int32';
915
+ case 'System.Int64': return 'System.Int64';
916
+ case 'System.DateTime': return 'System.DateTime';
917
+ case 'System.Byte[]': return 'System.Byte[]';
918
+ default: return 'System.String';
919
+ }
920
+ };
921
+ // ── UUID helper — use Node.js crypto for guaranteed RFC-4122 v4 format ──
922
+ const uuid = () => crypto.randomUUID();
923
+ // ── Build one AxReportDataSet XML entry ──
924
+ const buildDatasetXml = (ds) => {
925
+ const dpParamName = `${ds.dpClassName.toUpperCase()}_DynamicParameter`;
926
+ const contractDatasetParamsXml = (ds.contractParams || []).map(cp => {
927
+ const pn = `${ds.name}_ds_${cp.name}`;
928
+ const dt = cp.dataType || 'System.String';
929
+ return `\t\t\t\t<AxReportDataSetParameter>\n\t\t\t\t\t<Name>${pn}</Name>\n\t\t\t\t\t<Alias>${pn}</Alias>\n\t\t\t\t\t<DataType>${dt}</DataType>\n\t\t\t\t\t<Parameter>${pn}</Parameter>\n\t\t\t\t</AxReportDataSetParameter>`;
930
+ }).join('\n');
931
+ let fieldsXml;
932
+ if (ds.fields && ds.fields.length > 0) {
933
+ const entries = ds.fields.map(f => {
934
+ const alias = f.alias || `${ds.tmpTableName}.1.${f.name}`;
935
+ const capLine = f.caption ? `\n\t\t\t\t<Caption>${f.caption}</Caption>` : '';
936
+ const dtLine = f.dataType ? `\n\t\t\t\t<DataType>${f.dataType}</DataType>` : '';
937
+ const disableLine = f.disableAutoCreate ? `\n\t\t\t\t<DisableAutoCreateInDataRegion>true</DisableAutoCreateInDataRegion>` : '';
938
+ return [
939
+ `\t\t\t<AxReportDataSetField>`,
940
+ `\t\t\t\t<Name>${f.name}</Name>`,
941
+ `\t\t\t\t<Alias>${alias}</Alias>${capLine}${dtLine}${disableLine}`,
942
+ `\t\t\t\t<DisplayWidth>Auto</DisplayWidth>`,
943
+ `\t\t\t\t<UserDefined>false</UserDefined>`,
944
+ `\t\t\t</AxReportDataSetField>`,
945
+ ].join('\n');
946
+ });
947
+ fieldsXml = `\t\t\t<Fields>\n${entries.join('\n')}\n\t\t\t</Fields>`;
948
+ }
949
+ else {
950
+ // ⚠ No field definitions provided — dataset will have no columns visible in the
951
+ // D365FO Report Designer. Caller MUST pass a `fields` array listing all TmpTable
952
+ // columns that the report should expose (name, alias, dataType). Without this the
953
+ // designer shows an empty dataset and the RDL tablix has no fields to bind to.
954
+ fieldsXml = `\t\t\t<Fields />`;
955
+ }
956
+ return `\t\t<AxReportDataSet xmlns="">
957
+ \t\t\t<Name>${ds.name}</Name>
958
+ \t\t\t<DataSourceType>ReportDataProvider</DataSourceType>
959
+ \t\t\t<Query>SELECT * FROM ${ds.dpClassName}.${ds.tmpTableName}</Query>
960
+ \t\t\t<FieldGroups />
961
+ ${fieldsXml}
962
+ \t\t\t<Parameters>
963
+ \t\t\t\t<AxReportDataSetParameter>
964
+ \t\t\t\t\t<Name>AX_PartitionKey</Name>
965
+ \t\t\t\t\t<Alias>AX_PartitionKey</Alias>
966
+ \t\t\t\t\t<DataType>System.String</DataType>
967
+ \t\t\t\t\t<Parameter>AX_PartitionKey</Parameter>
968
+ \t\t\t\t</AxReportDataSetParameter>
969
+ \t\t\t\t<AxReportDataSetParameter>
970
+ \t\t\t\t\t<Name>AX_CompanyName</Name>
971
+ \t\t\t\t\t<Alias>AX_CompanyName</Alias>
972
+ \t\t\t\t\t<DataType>System.String</DataType>
973
+ \t\t\t\t\t<Parameter>AX_CompanyName</Parameter>
974
+ \t\t\t\t</AxReportDataSetParameter>
975
+ \t\t\t\t<AxReportDataSetParameter>
976
+ \t\t\t\t\t<Name>AX_UserContext</Name>
977
+ \t\t\t\t\t<Alias>AX_UserContext</Alias>
978
+ \t\t\t\t\t<DataType>System.String</DataType>
979
+ \t\t\t\t\t<Parameter>AX_UserContext</Parameter>
980
+ \t\t\t\t</AxReportDataSetParameter>
981
+ \t\t\t\t<AxReportDataSetParameter>
982
+ \t\t\t\t\t<Name>AX_RenderingCulture</Name>
983
+ \t\t\t\t\t<Alias>AX_RenderingCulture</Alias>
984
+ \t\t\t\t\t<DataType>System.String</DataType>
985
+ \t\t\t\t\t<Parameter>AX_RenderingCulture</Parameter>
986
+ \t\t\t\t</AxReportDataSetParameter>
987
+ \t\t\t\t<AxReportDataSetParameter>
988
+ \t\t\t\t\t<Name>AX_ReportContext</Name>
989
+ \t\t\t\t\t<Alias>AX_ReportContext</Alias>
990
+ \t\t\t\t\t<DataType>System.String</DataType>
991
+ \t\t\t\t\t<Parameter>AX_ReportContext</Parameter>
992
+ \t\t\t\t</AxReportDataSetParameter>
993
+ \t\t\t\t<AxReportDataSetParameter>
994
+ \t\t\t\t\t<Name>AX_RdpPreProcessedId</Name>
995
+ \t\t\t\t\t<Alias>AX_RdpPreProcessedId</Alias>
996
+ \t\t\t\t\t<DataType>System.String</DataType>
997
+ \t\t\t\t\t<Parameter>AX_RdpPreProcessedId</Parameter>
998
+ \t\t\t\t</AxReportDataSetParameter>
999
+ ${contractDatasetParamsXml ? contractDatasetParamsXml + '\n' : ''}\t\t\t\t<AxReportDataSetParameter>
1000
+ \t\t\t\t\t<Name>${dpParamName}</Name>
1001
+ \t\t\t\t\t<Alias>${dpParamName}</Alias>
1002
+ \t\t\t\t\t<DataType>Microsoft.Dynamics.AX.Framework.Services.Client.QueryMetadata</DataType>
1003
+ \t\t\t\t\t<Parameter>${dpParamName}</Parameter>
1004
+ \t\t\t\t</AxReportDataSetParameter>
1005
+ \t\t\t</Parameters>
1006
+ \t\t</AxReportDataSet>`;
1007
+ };
1008
+ const datasetsXml = datasets.map(buildDatasetXml).join('\n');
1009
+ // ── DefaultParameterGroup (uses first dataset's DP for DynamicParameter) ──
1010
+ const firstDs = datasets[0];
1011
+ const dpParamName = `${firstDs.dpClassName.toUpperCase()}_DynamicParameter`;
1012
+ const aotQueryLine = firstDs.aotQuery ? `\n\t\t\t\t<AOTQuery>${firstDs.aotQuery}</AOTQuery>` : '';
1013
+ // Contract parameters (from DataContract class with [DataMember] attributes)
1014
+ const contractParamsXml = (firstDs.contractParams || []).map(cp => {
1015
+ const dataTypeLine = cp.dataType ? `\n\t\t\t\t<DataType>${cp.dataType}</DataType>` : '';
1016
+ const promptLine = cp.label ? `\n\t\t\t\t<PromptString>${cp.label}</PromptString>` : '';
1017
+ return `\t\t\t<AxReportParameterBase xmlns=""
1018
+ \t\t\t\t\ti:type="AxReportParameter">
1019
+ \t\t\t\t<Name>${firstDs.name}_ds_${cp.name}</Name>${dataTypeLine}${promptLine}
1020
+ \t\t\t\t<DefaultValue />
1021
+ \t\t\t\t<Values />
1022
+ \t\t\t</AxReportParameterBase>`;
1023
+ }).join('\n');
1024
+ const defaultParamGroupXml = `\t<DefaultParameterGroup>
1025
+ \t\t<Name xmlns="">Parameters</Name>
1026
+ \t\t<ReportParameterBases xmlns="">
1027
+ \t\t\t<AxReportParameterBase xmlns=""
1028
+ \t\t\t\t\ti:type="AxReportParameter">
1029
+ \t\t\t\t<Name>AX_PartitionKey</Name>
1030
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1031
+ \t\t\t\t<Nullable>true</Nullable>
1032
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1033
+ \t\t\t\t<DefaultValue />
1034
+ \t\t\t\t<Values />
1035
+ \t\t\t</AxReportParameterBase>
1036
+ \t\t\t<AxReportParameterBase xmlns=""
1037
+ \t\t\t\t\ti:type="AxReportParameter">
1038
+ \t\t\t\t<Name>AX_CompanyName</Name>
1039
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1040
+ \t\t\t\t<DefaultValue />
1041
+ \t\t\t\t<Values />
1042
+ \t\t\t</AxReportParameterBase>
1043
+ \t\t\t<AxReportParameterBase xmlns=""
1044
+ \t\t\t\t\ti:type="AxReportParameter">
1045
+ \t\t\t\t<Name>AX_UserContext</Name>
1046
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1047
+ \t\t\t\t<Nullable>true</Nullable>
1048
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1049
+ \t\t\t\t<DefaultValue />
1050
+ \t\t\t\t<Values />
1051
+ \t\t\t</AxReportParameterBase>
1052
+ \t\t\t<AxReportParameterBase xmlns=""
1053
+ \t\t\t\t\ti:type="AxReportParameter">
1054
+ \t\t\t\t<Name>AX_RenderingCulture</Name>
1055
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1056
+ \t\t\t\t<Nullable>true</Nullable>
1057
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1058
+ \t\t\t\t<DefaultValue />
1059
+ \t\t\t\t<Values />
1060
+ \t\t\t</AxReportParameterBase>
1061
+ \t\t\t<AxReportParameterBase xmlns=""
1062
+ \t\t\t\t\ti:type="AxReportParameter">
1063
+ \t\t\t\t<Name>AX_ReportContext</Name>
1064
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1065
+ \t\t\t\t<Nullable>true</Nullable>
1066
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1067
+ \t\t\t\t<DefaultValue />
1068
+ \t\t\t\t<Values />
1069
+ \t\t\t</AxReportParameterBase>
1070
+ \t\t\t<AxReportParameterBase xmlns=""
1071
+ \t\t\t\t\ti:type="AxReportParameter">
1072
+ \t\t\t\t<Name>AX_RdpPreProcessedId</Name>
1073
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1074
+ \t\t\t\t<Nullable>true</Nullable>
1075
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1076
+ \t\t\t\t<DefaultValue />
1077
+ \t\t\t\t<Values />
1078
+ \t\t\t</AxReportParameterBase>
1079
+ ${contractParamsXml}${contractParamsXml ? '\n' : ''}\t\t\t<AxReportParameterBase xmlns=""
1080
+ \t\t\t\t\ti:type="AxReportParameter">
1081
+ \t\t\t\t<Name>${dpParamName}</Name>${aotQueryLine}
1082
+ \t\t\t\t<AllowBlank>true</AllowBlank>
1083
+ \t\t\t\t<DataType>Microsoft.Dynamics.AX.Framework.Services.Client.QueryMetadata</DataType>
1084
+ \t\t\t\t<Nullable>true</Nullable>
1085
+ \t\t\t\t<UserVisibility>Hidden</UserVisibility>
1086
+ \t\t\t\t<DefaultValue />
1087
+ \t\t\t\t<Values />
1088
+ \t\t\t</AxReportParameterBase>
1089
+ \t\t</ReportParameterBases>
1090
+ \t</DefaultParameterGroup>`;
1091
+ // ── Auto-generate RDL skeleton (2016 namespace, mirrors real D365FO reports) ──
1092
+ const buildRdlSkeleton = () => {
1093
+ const ns2016 = 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition';
1094
+ const nsRd = 'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner';
1095
+ // DataSources block — single shared AX data source
1096
+ const rdlDataSourcesXml = ` <DataSources>
1097
+ <DataSource Name="AutoGen__ReportDataProvider">
1098
+ <Transaction>true</Transaction>
1099
+ <ConnectionProperties>
1100
+ <DataProvider>AXREPORTDATAPROVIDER</DataProvider>
1101
+ <ConnectString />
1102
+ <IntegratedSecurity>true</IntegratedSecurity>
1103
+ </ConnectionProperties>
1104
+ <rd:DataSourceID>${uuid()}</rd:DataSourceID>
1105
+ </DataSource>
1106
+ </DataSources>`;
1107
+ // Build one RDL DataSet per AxReportDataSet
1108
+ const buildRdlDataset = (ds) => {
1109
+ const dsDpParam = `${ds.dpClassName.toUpperCase()}_DynamicParameter`;
1110
+ const contractParamNamesRdl = (ds.contractParams || []).map(cp => `${ds.name}_ds_${cp.name}`);
1111
+ const paramNames = [
1112
+ 'AX_PartitionKey', 'AX_CompanyName', 'AX_UserContext',
1113
+ 'AX_RenderingCulture', 'AX_ReportContext', 'AX_RdpPreProcessedId',
1114
+ ...contractParamNamesRdl,
1115
+ dsDpParam,
1116
+ ];
1117
+ const queryParams = paramNames
1118
+ .map(p => ` <QueryParameter Name="${p}">\n <Value>=Parameters!${p}.Value</Value>\n </QueryParameter>`)
1119
+ .join('\n');
1120
+ let rdlFields = '';
1121
+ if (ds.fields && ds.fields.length > 0) {
1122
+ const flines = ds.fields.map(f => {
1123
+ const alias = f.alias || `${ds.tmpTableName}.1.${f.name}`;
1124
+ return ` <Field Name="${f.name}">\n <DataField>${alias}</DataField>\n <rd:TypeName>${rdlType(f.dataType)}</rd:TypeName>\n </Field>`;
1125
+ });
1126
+ rdlFields = ` <Fields>\n${flines.join('\n')}\n </Fields>\n`;
1127
+ }
1128
+ return ` <DataSet Name="${ds.name}">
1129
+ <rd:DataSetID>${uuid()}</rd:DataSetID>
1130
+ <Query>
1131
+ <DataSourceName>AutoGen__ReportDataProvider</DataSourceName>
1132
+ <QueryParameters>
1133
+ ${queryParams}
1134
+ </QueryParameters>
1135
+ <CommandText>SELECT * FROM ${ds.dpClassName}.${ds.tmpTableName}</CommandText>
1136
+ <rd:UseGenericDesigner>true</rd:UseGenericDesigner>
1137
+ </Query>
1138
+ ${rdlFields} <rd:DataSetInfo>
1139
+ <rd:DataSetName>${ds.name}</rd:DataSetName>
1140
+ <rd:TableName>Fields</rd:TableName>
1141
+ <rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
1142
+ <rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
1143
+ <rd:TableAdapterName>FieldsTableAdapter</rd:TableAdapterName>
1144
+ </rd:DataSetInfo>
1145
+ </DataSet>`;
1146
+ };
1147
+ const rdlDatasetsXml = ` <DataSets>\n${datasets.map(buildRdlDataset).join('\n')}\n </DataSets>`;
1148
+ // ── Build a simple detail tablix for each dataset so the design is not empty ──
1149
+ const buildRdlTablix = (ds) => {
1150
+ if (!ds.fields || ds.fields.length === 0)
1151
+ return '';
1152
+ const n = ds.fields.length;
1153
+ const colW = +Math.min(1.5, 7 / n).toFixed(2);
1154
+ const totalW = +(colW * n).toFixed(2);
1155
+ const grp = `Details_${ds.name}`;
1156
+ const cols = ds.fields.map(() => ` <TablixColumn><Width>${colW}in</Width></TablixColumn>`).join('\n');
1157
+ const hCells = ds.fields.map(f => [
1158
+ ` <TablixCell><CellContents>`,
1159
+ ` <Textbox Name="Textbox_${f.name}_H">`,
1160
+ ` <CanGrow>true</CanGrow><Value>${f.name}</Value>`,
1161
+ ` <Style><FontWeight>Bold</FontWeight>`,
1162
+ ` <BackgroundColor>LightGrey</BackgroundColor>`,
1163
+ ` <Border><Style>Solid</Style></Border>`,
1164
+ ` <PaddingLeft>2pt</PaddingLeft><PaddingRight>2pt</PaddingRight>`,
1165
+ ` <PaddingTop>2pt</PaddingTop><PaddingBottom>2pt</PaddingBottom>`,
1166
+ ` </Style></Textbox>`,
1167
+ ` </CellContents></TablixCell>`,
1168
+ ].join('\n')).join('\n');
1169
+ const dCells = ds.fields.map(f => [
1170
+ ` <TablixCell><CellContents>`,
1171
+ ` <Textbox Name="Textbox_${f.name}">`,
1172
+ ` <CanGrow>true</CanGrow><Value>=Fields!${f.name}.Value</Value>`,
1173
+ ` <Style><Border><Style>Solid</Style></Border>`,
1174
+ ` <PaddingLeft>2pt</PaddingLeft><PaddingRight>2pt</PaddingRight>`,
1175
+ ` <PaddingTop>2pt</PaddingTop><PaddingBottom>2pt</PaddingBottom>`,
1176
+ ` </Style></Textbox>`,
1177
+ ` </CellContents></TablixCell>`,
1178
+ ].join('\n')).join('\n');
1179
+ const cMembers = ds.fields.map(() => ` <TablixMember />`).join('\n');
1180
+ return [
1181
+ ` <Tablix Name="Tablix_${ds.name}">`,
1182
+ ` <TablixBody>`,
1183
+ ` <TablixColumns>`,
1184
+ cols,
1185
+ ` </TablixColumns>`,
1186
+ ` <TablixRows>`,
1187
+ ` <TablixRow><Height>0.25in</Height><TablixCells>`,
1188
+ hCells,
1189
+ ` </TablixCells></TablixRow>`,
1190
+ ` <TablixRow><Height>0.25in</Height><TablixCells>`,
1191
+ dCells,
1192
+ ` </TablixCells></TablixRow>`,
1193
+ ` </TablixRows>`,
1194
+ ` </TablixBody>`,
1195
+ ` <TablixColumnHierarchy><TablixMembers>`,
1196
+ cMembers,
1197
+ ` </TablixMembers></TablixColumnHierarchy>`,
1198
+ ` <TablixRowHierarchy><TablixMembers>`,
1199
+ ` <TablixMember>`,
1200
+ ` <KeepWithGroup>After</KeepWithGroup>`,
1201
+ ` <RepeatOnNewPage>true</RepeatOnNewPage>`,
1202
+ ` </TablixMember>`,
1203
+ ` <TablixMember><Group Name="${grp}"><DataGroupName>${grp}</DataGroupName></Group></TablixMember>`,
1204
+ ` </TablixMembers></TablixRowHierarchy>`,
1205
+ ` <DataSetName>${ds.name}</DataSetName>`,
1206
+ ` <Top>0.5in</Top><Left>0.5in</Left>`,
1207
+ ` <Height>0.5in</Height><Width>${totalW}in</Width>`,
1208
+ ` <Style><Border><Style>Solid</Style></Border></Style>`,
1209
+ ` </Tablix>`,
1210
+ ].join('\n');
1211
+ };
1212
+ const rdlBodyItemsXml = datasets.map(buildRdlTablix).filter(Boolean).join('\n');
1213
+ const rdlBodyTag = rdlBodyItemsXml
1214
+ ? ` <ReportItems>\n${rdlBodyItemsXml}\n </ReportItems>`
1215
+ : ` <ReportItems />`;
1216
+ // ReportParameters — 6 AX system params + DynamicParameter + contract params (all hidden)
1217
+ const contractRdlParams = (firstDs.contractParams || []).map(cp => ({
1218
+ name: `${firstDs.name}_ds_${cp.name}`,
1219
+ nullable: true, blank: true, usedInQuery: true,
1220
+ }));
1221
+ const rdlParamDefs = [
1222
+ { name: 'AX_PartitionKey', nullable: true, blank: true, usedInQuery: false },
1223
+ { name: 'AX_CompanyName', nullable: false, blank: false, usedInQuery: false },
1224
+ { name: 'AX_UserContext', nullable: true, blank: true, usedInQuery: false },
1225
+ { name: 'AX_RenderingCulture', nullable: true, blank: true, usedInQuery: false },
1226
+ { name: 'AX_ReportContext', nullable: true, blank: true, usedInQuery: true },
1227
+ { name: 'AX_RdpPreProcessedId', nullable: true, blank: true, usedInQuery: false },
1228
+ { name: dpParamName, nullable: true, blank: true, usedInQuery: false },
1229
+ ...contractRdlParams,
1230
+ ];
1231
+ const rdlParamsXml = ` <ReportParameters>\n` +
1232
+ rdlParamDefs.map(p => {
1233
+ const nullLine = p.nullable ? `\n <Nullable>true</Nullable>` : '';
1234
+ const blankLine = p.blank ? `\n <AllowBlank>true</AllowBlank>` : '';
1235
+ const usedLine = p.usedInQuery ? `\n <UsedInQuery>true</UsedInQuery>` : '';
1236
+ return ` <ReportParameter Name="${p.name}">\n <DataType>String</DataType>${nullLine}${blankLine}\n <Prompt>${p.name}</Prompt>\n <Hidden>true</Hidden>${usedLine}\n </ReportParameter>`;
1237
+ }).join('\n') + `\n </ReportParameters>`;
1238
+ // ReportParametersLayout
1239
+ const cellDefs = rdlParamDefs
1240
+ .map((p, i) => ` <CellDefinition>\n <ColumnIndex>${i}</ColumnIndex>\n <RowIndex>0</RowIndex>\n <ParameterName>${p.name}</ParameterName>\n </CellDefinition>`)
1241
+ .join('\n');
1242
+ const rdlParamLayoutXml = ` <ReportParametersLayout>
1243
+ <GridLayoutDefinition>
1244
+ <NumberOfColumns>${rdlParamDefs.length}</NumberOfColumns>
1245
+ <NumberOfRows>1</NumberOfRows>
1246
+ <CellDefinitions>
1247
+ ${cellDefs}
1248
+ </CellDefinitions>
1249
+ </GridLayoutDefinition>
1250
+ </ReportParametersLayout>`;
1251
+ return `<?xml version="1.0" encoding="utf-8"?>
1252
+ <Report xmlns="${ns2016}" xmlns:rd="${nsRd}">
1253
+ <AutoRefresh>0</AutoRefresh>
1254
+ ${rdlDataSourcesXml}
1255
+ ${rdlDatasetsXml}
1256
+ <ReportSections>
1257
+ <ReportSection>
1258
+ <Body>
1259
+ ${rdlBodyTag}
1260
+ <Height>1in</Height>
1261
+ <Style>
1262
+ <Border>
1263
+ <Style>None</Style>
1264
+ </Border>
1265
+ </Style>
1266
+ </Body>
1267
+ <Width>7.5in</Width>
1268
+ <Page>
1269
+ <PageHeight>11.69in</PageHeight>
1270
+ <PageWidth>8.27in</PageWidth>
1271
+ <InteractiveHeight>11in</InteractiveHeight>
1272
+ <InteractiveWidth>8.5in</InteractiveWidth>
1273
+ <LeftMargin>0.2in</LeftMargin>
1274
+ <TopMargin>0.2in</TopMargin>
1275
+ <Style />
1276
+ </Page>
1277
+ </ReportSection>
1278
+ </ReportSections>
1279
+ ${rdlParamsXml}
1280
+ ${rdlParamLayoutXml}
1281
+ <Language>en-US</Language>
1282
+ <rd:ReportUnitType>Inch</rd:ReportUnitType>
1283
+ <rd:ReportID>${uuid()}</rd:ReportID>
1284
+ </Report>`;
1285
+ };
1286
+ // ── Design block ──
1287
+ const captionLine = properties?.caption ? `\n\t\t\t<Caption>${properties.caption}</Caption>` : '';
1288
+ const styleLine = properties?.style ? `\n\t\t\t<Style>${properties.style}</Style>` : '';
1289
+ const rdlContent = properties?.rdlContent;
1290
+ // Sanitize: fix old-schema <Header> inside <TablixMember> — renamed to <TablixHeader> in 2016 RDL.
1291
+ // This handles AI-generated or older-tool-generated RDL that still uses the pre-2016 element name.
1292
+ const rdl = (rdlContent || buildRdlSkeleton())
1293
+ .replace(/<Header>/g, '<TablixHeader>')
1294
+ .replace(/<\/Header>/g, '</TablixHeader>');
1295
+ const textElement = `\n\t\t\t<Text><![CDATA[${rdl}]]></Text>`;
1296
+ return `<?xml version="1.0" encoding="utf-8"?>
1297
+ <AxReport xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V2">
1298
+ \t<Name>${reportName}</Name>
1299
+ \t<DataMethods />
1300
+ \t<DataSets>
1301
+ ${datasetsXml}
1302
+ \t</DataSets>
1303
+ ${defaultParamGroupXml}
1304
+ \t<Designs>
1305
+ \t\t<AxReportDesign xmlns=""
1306
+ \t\t\t\ti:type="AxReportPrecisionDesign">
1307
+ \t\t\t<Name>${designName}</Name>${captionLine}${styleLine}${textElement}
1308
+ \t\t\t<DisableIndividualTransformation>
1309
+ \t\t\t\t<Name>DisableIndividualTransformation</Name>
1310
+ \t\t\t</DisableIndividualTransformation>
1311
+ \t\t</AxReportDesign>
1312
+ \t</Designs>
1313
+ \t<EmbeddedImages />
1314
+ </AxReport>`;
1315
+ }
1316
+ /**
1317
+ * Generate XML based on object type
1318
+ */
1319
+ static generate(objectType, objectName, sourceCode, properties) {
1320
+ switch (objectType) {
1321
+ case 'class':
1322
+ return this.generateAxClassXml(objectName, sourceCode, properties);
1323
+ case 'class-extension':
1324
+ return this.generateAxClassExtensionXml(objectName, sourceCode, properties);
1325
+ case 'table': {
1326
+ // sourceCode is not used for tables directly, but Copilot may pass field
1327
+ // definitions as a JSON string in sourceCode. Try to parse and merge into properties.
1328
+ let mergedProperties = properties;
1329
+ if (sourceCode && sourceCode.trim().startsWith('{')) {
1330
+ try {
1331
+ const parsed = JSON.parse(sourceCode);
1332
+ if (parsed && (Array.isArray(parsed.fields) || parsed.label || parsed.tableGroup)) {
1333
+ mergedProperties = { ...parsed, ...properties }; // explicit properties win
1334
+ console.error('[create_d365fo_file] Parsed table field definitions from sourceCode JSON');
1335
+ }
1336
+ }
1337
+ catch {
1338
+ // Not valid JSON — ignore
1339
+ }
1340
+ }
1341
+ return this.generateAxTableXml(objectName, mergedProperties);
1342
+ }
1343
+ case 'enum':
1344
+ return this.generateAxEnumXml(objectName, properties);
1345
+ case 'form':
1346
+ return this.generateAxFormXml(objectName, properties);
1347
+ case 'query':
1348
+ return this.generateAxQueryXml(objectName, properties);
1349
+ case 'view':
1350
+ return this.generateAxViewXml(objectName, properties);
1351
+ case 'map':
1352
+ return this.generateAxMapXml(objectName, properties);
1353
+ case 'data-entity':
1354
+ return this.generateAxDataEntityXml(objectName, properties);
1355
+ case 'report':
1356
+ return this.generateAxReportXml(objectName, properties);
1357
+ case 'edt':
1358
+ return this.generateAxEdtXml(objectName, properties);
1359
+ case 'table-extension':
1360
+ return this.generateAxTableExtensionXml(objectName, properties);
1361
+ case 'form-extension':
1362
+ return this.generateAxFormExtensionXml(objectName);
1363
+ case 'edt-extension':
1364
+ return this.generateAxSimpleExtensionXml('AxEdtExtension', objectName);
1365
+ case 'enum-extension':
1366
+ return this.generateAxEnumExtensionXml(objectName, properties);
1367
+ case 'data-entity-extension':
1368
+ return this.generateAxSimpleExtensionXml('AxDataEntityViewExtension', objectName);
1369
+ case 'menu-item-display':
1370
+ case 'menu-item-action':
1371
+ case 'menu-item-output':
1372
+ return this.generateAxMenuItemXml(objectType, objectName, properties);
1373
+ case 'menu-item-display-extension':
1374
+ return this.generateAxSimpleExtensionXml('AxMenuItemDisplayExtension', objectName);
1375
+ case 'menu-item-action-extension':
1376
+ return this.generateAxSimpleExtensionXml('AxMenuItemActionExtension', objectName);
1377
+ case 'menu-item-output-extension':
1378
+ return this.generateAxSimpleExtensionXml('AxMenuItemOutputExtension', objectName);
1379
+ case 'menu':
1380
+ return this.generateAxMenuXml(objectName, properties);
1381
+ case 'menu-extension':
1382
+ return this.generateAxMenuExtensionXml(objectName);
1383
+ case 'security-privilege':
1384
+ return this.generateAxSecurityPrivilegeXml(objectName, properties);
1385
+ case 'security-duty':
1386
+ return this.generateAxSecurityDutyXml(objectName, properties);
1387
+ case 'security-role':
1388
+ return this.generateAxSecurityRoleXml(objectName, properties);
1389
+ case 'security-duty-extension':
1390
+ return this.generateAxSecurityDutyExtensionXml(objectName, properties);
1391
+ case 'security-role-extension':
1392
+ return this.generateAxSecurityRoleExtensionXml(objectName, properties);
1393
+ case 'business-event':
1394
+ return XmlTemplateGenerator.generateBusinessEventXml(objectName, properties);
1395
+ case 'tile':
1396
+ return XmlTemplateGenerator.generateAxTileXml(objectName, properties);
1397
+ case 'kpi':
1398
+ return XmlTemplateGenerator.generateAxKpiXml(objectName, properties);
1399
+ default:
1400
+ throw new Error(`Unsupported object type: ${objectType}`);
1401
+ }
1402
+ }
1403
+ /**
1404
+ * Sanitize AxQuery XML — ensures xmlns="" and i:type="AxQuerySimple" are present
1405
+ * on the root <AxQuery> element. D365FO deserializer requires both attributes.
1406
+ */
1407
+ static sanitizeQueryXml(xml) {
1408
+ return xml.replace(/<AxQuery(\s[^>]*)?>/, (_match, attrs) => {
1409
+ let a = attrs || '';
1410
+ if (!a.includes('xmlns=""')) {
1411
+ a += ' xmlns=""';
1412
+ }
1413
+ if (!a.includes('i:type="AxQuerySimple"')) {
1414
+ a += '\n\ti:type="AxQuerySimple"';
1415
+ }
1416
+ return `<AxQuery${a}>`;
1417
+ });
1418
+ }
1419
+ /**
1420
+ * Sanitize AxReport XML to guarantee the structural elements required by the D365FO
1421
+ * Visual Studio Designer metadata loader, regardless of whether the XML was generated
1422
+ * by the template or supplied verbatim by a caller via the xmlContent parameter.
1423
+ *
1424
+ * Required invariants:
1425
+ * 1. xmlns="Microsoft.Dynamics.AX.Metadata.V2" on <AxReport> root
1426
+ * 2. <DataMethods /> directly after <Name>…</Name>
1427
+ * 3. xmlns="" on every <AxReportDataSet> child element (namespace reset)
1428
+ * 4. </AxReport> closing tag present (guard against truncated XML)
1429
+ * 5. <AxReportDesign> has xmlns="" and i:type="AxReportPrecisionDesign" attributes
1430
+ * (VS Designer won't show Designs sub-nodes without these)
1431
+ */
1432
+ /**
1433
+ * Sanitize AxEnum XML — fixes common AI-generator mistakes that cause VS2022 to
1434
+ * silently ignore enum values or refuse to open the file:
1435
+ *
1436
+ * 1. <Values>…</Values> → <EnumValues>…</EnumValues>
1437
+ * AI models frequently map the JSON `enumValues` array to a plain <Values> wrapper;
1438
+ * D365FO deserializer requires <EnumValues>.
1439
+ *
1440
+ * 2. <AxEnum> without xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
1441
+ * The attribute is required for the i:type resolution inside the file.
1442
+ *
1443
+ * 3. More than 251 <AxEnumValue> elements — D365FO compiler hard limit.
1444
+ */
1445
+ static sanitizeEnumXml(xml) {
1446
+ // 1. Rename <Values> container to <EnumValues>
1447
+ if (/<Values>/.test(xml) && !/<EnumValues>/.test(xml)) {
1448
+ xml = xml.replace(/<Values>/g, '<EnumValues>').replace(/<\/Values>/g, '</EnumValues>');
1449
+ console.error('[sanitizeEnumXml] Renamed <Values> → <EnumValues>');
1450
+ }
1451
+ // 2. Add xmlns:i to <AxEnum> root if missing
1452
+ if (!xml.includes('xmlns:i=')) {
1453
+ xml = xml.replace(/(<AxEnum)(\s|>)/, '$1 xmlns:i="http://www.w3.org/2001/XMLSchema-instance"$2');
1454
+ console.error('[sanitizeEnumXml] Added xmlns:i to <AxEnum>');
1455
+ }
1456
+ // 3. Validate max 251 enum values (D365FO compiler hard limit, MS Learn confirmed)
1457
+ const valueCount = (xml.match(/<AxEnumValue>/g) ?? []).length;
1458
+ if (valueCount > 251) {
1459
+ console.error(`[sanitizeEnumXml] ⚠️ WARNING: ${valueCount} enum values detected — D365FO supports max 251 (0–250). ` +
1460
+ `The compiler will reject this file. Consider splitting into multiple enums or using a class hierarchy.`);
1461
+ }
1462
+ return xml;
1463
+ }
1464
+ /**
1465
+ * Sanitize AxTable XML to ensure correct D365FO field element format.
1466
+ *
1467
+ * D365FO requires fields as:
1468
+ * <AxTableField xmlns=""
1469
+ * i:type="AxTableFieldString"> ... </AxTableField>
1470
+ *
1471
+ * AI generators often emit the shorter form:
1472
+ * <AxTableFieldString> ... </AxTableFieldString>
1473
+ *
1474
+ * This method also ensures <FullTextIndexes /> is present between </Fields> and <Indexes>.
1475
+ */
1476
+ static sanitizeTableXml(xml) {
1477
+ const fieldTypes = [
1478
+ 'AxTableFieldString', 'AxTableFieldInt', 'AxTableFieldInt64',
1479
+ 'AxTableFieldReal', 'AxTableFieldDate', 'AxTableFieldUtcDateTime',
1480
+ 'AxTableFieldEnum', 'AxTableFieldGuid', 'AxTableFieldContainer',
1481
+ ];
1482
+ for (const ft of fieldTypes) {
1483
+ // Opening tag: <AxTableFieldString ...> → <AxTableField xmlns="" i:type="AxTableFieldString" ...>
1484
+ // Only replace if NOT already inside a correct <AxTableField xmlns="" i:type="..."> wrapper
1485
+ const openRe = new RegExp(`<${ft}(\\s[^>]*)?>`, 'g');
1486
+ xml = xml.replace(openRe, (_match, attrs) => {
1487
+ const extra = attrs ? attrs : '';
1488
+ return `<AxTableField xmlns=""\n\t\t\ti:type="${ft}"${extra}>`;
1489
+ });
1490
+ // Closing tag
1491
+ xml = xml.replace(new RegExp(`<\\/${ft}>`, 'g'), '</AxTableField>');
1492
+ }
1493
+ // Ensure <FullTextIndexes /> is present between </Fields> and <Indexes>
1494
+ if (!xml.includes('<FullTextIndexes')) {
1495
+ xml = xml.replace('</Fields>\n\t<Indexes', '</Fields>\n\t<FullTextIndexes />\n\t<Indexes');
1496
+ xml = xml.replace('</Fields>\n<Indexes', '</Fields>\n<FullTextIndexes />\n<Indexes');
1497
+ }
1498
+ return xml;
1499
+ }
1500
+ static sanitizeReportXml(xml) {
1501
+ // 1. Ensure xmlns="Microsoft.Dynamics.AX.Metadata.V2" on <AxReport> opening tag
1502
+ if (!xml.includes('xmlns="Microsoft.Dynamics.AX.Metadata.V2"')) {
1503
+ xml = xml.replace(/<AxReport(\s[^>]*)?>/, (match) => {
1504
+ // Insert the namespace attribute before the closing > of the tag
1505
+ return match.slice(0, -1) + ' xmlns="Microsoft.Dynamics.AX.Metadata.V2">';
1506
+ });
1507
+ console.error('[sanitizeReportXml] Added xmlns="Microsoft.Dynamics.AX.Metadata.V2" to <AxReport>');
1508
+ }
1509
+ // 2. Ensure <DataMethods /> exists directly after the top-level <Name>
1510
+ if (!xml.includes('<DataMethods')) {
1511
+ // Match only the first <Name>…</Name> (the report's own name, not nested ones)
1512
+ xml = xml.replace(/(<Name>[^<]*<\/Name>)/, '$1\n\t<DataMethods />');
1513
+ console.error('[sanitizeReportXml] Inserted missing <DataMethods />');
1514
+ }
1515
+ // 3. Ensure xmlns="" on each <AxReportDataSet> (bare tag without the attribute)
1516
+ if (xml.includes('<AxReportDataSet>')) {
1517
+ xml = xml.replace(/<AxReportDataSet>/g, '<AxReportDataSet xmlns="">');
1518
+ console.error('[sanitizeReportXml] Added xmlns="" to <AxReportDataSet> elements');
1519
+ }
1520
+ // 4. Ensure </AxReport> closing tag is present (guard against truncated XML)
1521
+ const trimmed = xml.trimEnd();
1522
+ if (!trimmed.endsWith('</AxReport>')) {
1523
+ xml = trimmed + '\n</AxReport>';
1524
+ console.error('[sanitizeReportXml] Appended missing </AxReport> closing tag');
1525
+ }
1526
+ // 5. Ensure <AxReportDesign> has xmlns="" and i:type="AxReportPrecisionDesign"
1527
+ // VS Designer requires both attributes to render the Designs sub-tree correctly.
1528
+ // Match bare <AxReportDesign> or one that is already partially attributed.
1529
+ xml = xml.replace(/<AxReportDesign(\s[^>]*)?>/, (match, attrs) => {
1530
+ const current = attrs || '';
1531
+ let updated = current;
1532
+ if (!updated.includes('xmlns=""')) {
1533
+ updated = ` xmlns=""${updated}`;
1534
+ }
1535
+ if (!updated.includes('i:type=')) {
1536
+ updated += `\n\t\t\t\ti:type="AxReportPrecisionDesign"`;
1537
+ }
1538
+ if (updated === current)
1539
+ return match; // nothing changed — idempotent
1540
+ console.error('[sanitizeReportXml] Fixed <AxReportDesign> attributes (xmlns="" + i:type)');
1541
+ return `<AxReportDesign${updated}>`;
1542
+ });
1543
+ // 6. Ensure <Parameters> block inside <AxReportDataSet> for real RDP datasets.
1544
+ // Skipped for stub/minimal datasets that have no <DataSourceType>.
1545
+ if (xml.includes('<DataSourceType>') && !xml.includes('<Parameters>')) {
1546
+ const axDatasetParams = '\t\t\t<Parameters>\n' +
1547
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1548
+ '\t\t\t\t\t<Name>AX_PartitionKey</Name>\n' +
1549
+ '\t\t\t\t\t<Alias>AX_PartitionKey</Alias>\n' +
1550
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1551
+ '\t\t\t\t\t<Parameter>AX_PartitionKey</Parameter>\n' +
1552
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1553
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1554
+ '\t\t\t\t\t<Name>AX_CompanyName</Name>\n' +
1555
+ '\t\t\t\t\t<Alias>AX_CompanyName</Alias>\n' +
1556
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1557
+ '\t\t\t\t\t<Parameter>AX_CompanyName</Parameter>\n' +
1558
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1559
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1560
+ '\t\t\t\t\t<Name>AX_UserContext</Name>\n' +
1561
+ '\t\t\t\t\t<Alias>AX_UserContext</Alias>\n' +
1562
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1563
+ '\t\t\t\t\t<Parameter>AX_UserContext</Parameter>\n' +
1564
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1565
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1566
+ '\t\t\t\t\t<Name>AX_RenderingCulture</Name>\n' +
1567
+ '\t\t\t\t\t<Alias>AX_RenderingCulture</Alias>\n' +
1568
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1569
+ '\t\t\t\t\t<Parameter>AX_RenderingCulture</Parameter>\n' +
1570
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1571
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1572
+ '\t\t\t\t\t<Name>AX_ReportContext</Name>\n' +
1573
+ '\t\t\t\t\t<Alias>AX_ReportContext</Alias>\n' +
1574
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1575
+ '\t\t\t\t\t<Parameter>AX_ReportContext</Parameter>\n' +
1576
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1577
+ '\t\t\t\t<AxReportDataSetParameter>\n' +
1578
+ '\t\t\t\t\t<Name>AX_RdpPreProcessedId</Name>\n' +
1579
+ '\t\t\t\t\t<Alias>AX_RdpPreProcessedId</Alias>\n' +
1580
+ '\t\t\t\t\t<DataType>System.String</DataType>\n' +
1581
+ '\t\t\t\t\t<Parameter>AX_RdpPreProcessedId</Parameter>\n' +
1582
+ '\t\t\t\t</AxReportDataSetParameter>\n' +
1583
+ '\t\t\t</Parameters>';
1584
+ if (xml.includes('</Fields>')) {
1585
+ xml = xml.replace('</Fields>', `</Fields>\n${axDatasetParams}`);
1586
+ }
1587
+ else if (xml.includes('<Fields />')) {
1588
+ xml = xml.replace('<Fields />', `<Fields />\n${axDatasetParams}`);
1589
+ }
1590
+ else {
1591
+ xml = xml.replace('</AxReportDataSet>', `${axDatasetParams}\n\t\t</AxReportDataSet>`);
1592
+ }
1593
+ console.error('[sanitizeReportXml] Added missing <Parameters> to <AxReportDataSet>');
1594
+ }
1595
+ // 7. Ensure <DefaultParameterGroup> before <Designs> for real RDP datasets.
1596
+ if (xml.includes('<DataSourceType>') && !xml.includes('<DefaultParameterGroup>') && xml.includes('<Designs>')) {
1597
+ const defaultParamGroup = '\t<DefaultParameterGroup>\n' +
1598
+ '\t\t<Name xmlns="">Parameters</Name>\n' +
1599
+ '\t\t<ReportParameterBases xmlns="">\n' +
1600
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1601
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1602
+ '\t\t\t\t<Name>AX_PartitionKey</Name>\n' +
1603
+ '\t\t\t\t<AllowBlank>true</AllowBlank>\n' +
1604
+ '\t\t\t\t<Nullable>true</Nullable>\n' +
1605
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1606
+ '\t\t\t\t<DefaultValue />\n' +
1607
+ '\t\t\t\t<Values />\n' +
1608
+ '\t\t\t</AxReportParameterBase>\n' +
1609
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1610
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1611
+ '\t\t\t\t<Name>AX_CompanyName</Name>\n' +
1612
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1613
+ '\t\t\t\t<DefaultValue />\n' +
1614
+ '\t\t\t\t<Values />\n' +
1615
+ '\t\t\t</AxReportParameterBase>\n' +
1616
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1617
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1618
+ '\t\t\t\t<Name>AX_UserContext</Name>\n' +
1619
+ '\t\t\t\t<AllowBlank>true</AllowBlank>\n' +
1620
+ '\t\t\t\t<Nullable>true</Nullable>\n' +
1621
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1622
+ '\t\t\t\t<DefaultValue />\n' +
1623
+ '\t\t\t\t<Values />\n' +
1624
+ '\t\t\t</AxReportParameterBase>\n' +
1625
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1626
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1627
+ '\t\t\t\t<Name>AX_RenderingCulture</Name>\n' +
1628
+ '\t\t\t\t<AllowBlank>true</AllowBlank>\n' +
1629
+ '\t\t\t\t<Nullable>true</Nullable>\n' +
1630
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1631
+ '\t\t\t\t<DefaultValue />\n' +
1632
+ '\t\t\t\t<Values />\n' +
1633
+ '\t\t\t</AxReportParameterBase>\n' +
1634
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1635
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1636
+ '\t\t\t\t<Name>AX_ReportContext</Name>\n' +
1637
+ '\t\t\t\t<AllowBlank>true</AllowBlank>\n' +
1638
+ '\t\t\t\t<Nullable>true</Nullable>\n' +
1639
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1640
+ '\t\t\t\t<DefaultValue />\n' +
1641
+ '\t\t\t\t<Values />\n' +
1642
+ '\t\t\t</AxReportParameterBase>\n' +
1643
+ '\t\t\t<AxReportParameterBase xmlns=""\n' +
1644
+ '\t\t\t\t\ti:type="AxReportParameter">\n' +
1645
+ '\t\t\t\t<Name>AX_RdpPreProcessedId</Name>\n' +
1646
+ '\t\t\t\t<AllowBlank>true</AllowBlank>\n' +
1647
+ '\t\t\t\t<Nullable>true</Nullable>\n' +
1648
+ '\t\t\t\t<UserVisibility>Hidden</UserVisibility>\n' +
1649
+ '\t\t\t\t<DefaultValue />\n' +
1650
+ '\t\t\t\t<Values />\n' +
1651
+ '\t\t\t</AxReportParameterBase>\n' +
1652
+ '\t\t</ReportParameterBases>\n' +
1653
+ '\t</DefaultParameterGroup>';
1654
+ xml = xml.replace('<Designs>', `${defaultParamGroup}\n\t<Designs>`);
1655
+ console.error('[sanitizeReportXml] Added missing <DefaultParameterGroup>');
1656
+ }
1657
+ // 8. Fix embedded RDL structural issues based on the SSRS namespace version:
1658
+ // 2008/01 — <PageHeader>/<PageFooter> must be inside <Page> (direct child of <Report>).
1659
+ // 2010/01+ (2010, 2016, future) — <Body> and <Page> must NOT be direct children of
1660
+ // <Report>; they must be wrapped in:
1661
+ // <ReportSections><ReportSection>...</ReportSection></ReportSections>
1662
+ // Placing <Page> directly under <Report> causes:
1663
+ // "Deserialization failed: invalid child element 'Page'" in VS Designer.
1664
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1665
+ const is2008 = rdl.includes('reporting/2008/01/reportdefinition');
1666
+ // Any SSRS namespace newer than 2008 requires ReportSections wrapping
1667
+ const isModernRdl = !is2008 && /reporting\/20\d\d\/\d\d\/reportdefinition/.test(rdl);
1668
+ const is2010 = isModernRdl; // kept for branch clarity below
1669
+ let fixedRdl = rdl;
1670
+ let changed = false;
1671
+ if (is2010 && !rdl.includes('<ReportSections>')) {
1672
+ // 2010 schema: collect any stray Body/Page/PageHeader/PageFooter that are direct
1673
+ // children of <Report>, then wrap them in ReportSections/ReportSection.
1674
+ let pageEl = '';
1675
+ const existingPageMatch = fixedRdl.match(/<Page(?:\s[^>]*)?>([\s\S]*?)<\/Page>/);
1676
+ if (existingPageMatch) {
1677
+ pageEl = existingPageMatch[0];
1678
+ fixedRdl = fixedRdl.replace(existingPageMatch[0], '');
1679
+ // PageHeader/PageFooter may still be direct children of <Report> (outside the <Page>
1680
+ // element we just extracted). Inject them into pageEl before </Page>.
1681
+ let extraPageContent = '';
1682
+ const phMatch = fixedRdl.match(/<PageHeader[\s\S]*?<\/PageHeader>/);
1683
+ if (phMatch && !pageEl.includes('<PageHeader')) {
1684
+ extraPageContent += phMatch[0];
1685
+ fixedRdl = fixedRdl.replace(phMatch[0], '');
1686
+ }
1687
+ const pfMatch = fixedRdl.match(/<PageFooter[\s\S]*?<\/PageFooter>/);
1688
+ if (pfMatch && !pageEl.includes('<PageFooter')) {
1689
+ extraPageContent += (extraPageContent ? '\n' : '') + pfMatch[0];
1690
+ fixedRdl = fixedRdl.replace(pfMatch[0], '');
1691
+ }
1692
+ if (extraPageContent) {
1693
+ pageEl = pageEl.replace('</Page>', extraPageContent.trim() + '\n</Page>');
1694
+ console.error('[sanitizeReportXml] Moved stray <PageHeader>/<PageFooter> into existing <Page> (2010 RDL)');
1695
+ }
1696
+ }
1697
+ else {
1698
+ let pageInner = '';
1699
+ const phMatch = fixedRdl.match(/<PageHeader[\s\S]*?<\/PageHeader>/);
1700
+ if (phMatch) {
1701
+ pageInner += phMatch[0];
1702
+ fixedRdl = fixedRdl.replace(phMatch[0], '');
1703
+ }
1704
+ const pfMatch = fixedRdl.match(/<PageFooter[\s\S]*?<\/PageFooter>/);
1705
+ if (pfMatch) {
1706
+ pageInner += (pageInner ? '\n' : '') + pfMatch[0];
1707
+ fixedRdl = fixedRdl.replace(pfMatch[0], '');
1708
+ }
1709
+ if (pageInner)
1710
+ pageEl = '<Page>\n' + pageInner.trim() + '\n</Page>';
1711
+ }
1712
+ const bodyMatch = fixedRdl.match(/<Body[\s\S]*?<\/Body>/);
1713
+ let sectionContent = '';
1714
+ if (bodyMatch) {
1715
+ sectionContent += bodyMatch[0];
1716
+ fixedRdl = fixedRdl.replace(bodyMatch[0], '');
1717
+ }
1718
+ if (pageEl)
1719
+ sectionContent += (sectionContent ? '\n' : '') + pageEl;
1720
+ if (sectionContent) {
1721
+ const reportSections = '<ReportSections>\n<ReportSection>\n' + sectionContent.trim() + '\n</ReportSection>\n</ReportSections>';
1722
+ fixedRdl = fixedRdl.includes('</Report>')
1723
+ ? fixedRdl.replace('</Report>', reportSections + '\n</Report>')
1724
+ : fixedRdl + '\n' + reportSections;
1725
+ changed = true;
1726
+ const rdlVersion = rdl.match(/reporting\/(20\d\d\/\d\d)\/reportdefinition/)?.[1] ?? 'modern';
1727
+ console.error(`[sanitizeReportXml] Wrapped Body+Page in <ReportSections>/<ReportSection> for ${rdlVersion} RDL`);
1728
+ }
1729
+ }
1730
+ else if (is2008 && (rdl.includes('<PageHeader') || rdl.includes('<PageFooter'))) {
1731
+ // 2008 schema: <PageHeader>/<PageFooter> must be inside <Page>, not direct children of <Report>.
1732
+ // A real D365FO RDL always has a <Page> element (PageWidth/Height/Margins) but PageHeader is
1733
+ // still a sibling — the old guard `!rdl.match(/<Page...>/)` incorrectly skipped this case.
1734
+ // Strategy:
1735
+ // a) If <Page> already exists — inject PageHeader/PageFooter before </Page>.
1736
+ // b) If <Page> doesn't exist — create one after </Body>.
1737
+ const pageMatch = fixedRdl.match(/<Page(?:\s[^>]*)?>[\s\S]*?<\/Page>/);
1738
+ const alreadyInPage = !!pageMatch &&
1739
+ (pageMatch[0].includes('<PageHeader') || pageMatch[0].includes('<PageFooter'));
1740
+ if (!alreadyInPage) {
1741
+ let pageInner = '';
1742
+ const phMatch = fixedRdl.match(/<PageHeader[\s\S]*?<\/PageHeader>/);
1743
+ if (phMatch) {
1744
+ pageInner += phMatch[0];
1745
+ fixedRdl = fixedRdl.replace(phMatch[0], '');
1746
+ }
1747
+ const pfMatch = fixedRdl.match(/<PageFooter[\s\S]*?<\/PageFooter>/);
1748
+ if (pfMatch) {
1749
+ pageInner += (pageInner ? '\n' : '') + pfMatch[0];
1750
+ fixedRdl = fixedRdl.replace(pfMatch[0], '');
1751
+ }
1752
+ if (pageInner) {
1753
+ if (pageMatch) {
1754
+ // Inject into the existing <Page> before </Page>
1755
+ const updatedPage = pageMatch[0].replace('</Page>', pageInner.trim() + '\n</Page>');
1756
+ fixedRdl = fixedRdl.replace(pageMatch[0], updatedPage);
1757
+ }
1758
+ else {
1759
+ // No existing <Page> — create one after </Body>
1760
+ const pageEl = '<Page>\n' + pageInner.trim() + '\n</Page>';
1761
+ fixedRdl = fixedRdl.includes('</Body>')
1762
+ ? fixedRdl.replace('</Body>', '</Body>\n' + pageEl)
1763
+ : fixedRdl.replace('</Report>', pageEl + '\n</Report>');
1764
+ }
1765
+ changed = true;
1766
+ console.error('[sanitizeReportXml] Moved <PageHeader>/<PageFooter> inside <Page> in 2008 RDL');
1767
+ }
1768
+ }
1769
+ }
1770
+ if (!changed)
1771
+ return _whole;
1772
+ return open + fixedRdl + close;
1773
+ });
1774
+ // 9. Fix wrong margin element names inside embedded RDL.
1775
+ // Some AI-generated RDLs use CSS-style names (MarginTop, MarginLeft, …) instead of
1776
+ // the correct SSRS RDL names (TopMargin, LeftMargin, …). All SSRS namespace versions
1777
+ // require the XMargin form — MarginX causes "invalid child element 'MarginTop'" in
1778
+ // VS Designer even though the value and namespace are otherwise correct.
1779
+ if (xml.includes('<MarginTop>') || xml.includes('<MarginBottom>') ||
1780
+ xml.includes('<MarginLeft>') || xml.includes('<MarginRight>')) {
1781
+ xml = xml
1782
+ .replace(/<MarginTop>/g, '<TopMargin>').replace(/<\/MarginTop>/g, '</TopMargin>')
1783
+ .replace(/<MarginBottom>/g, '<BottomMargin>').replace(/<\/MarginBottom>/g, '</BottomMargin>')
1784
+ .replace(/<MarginLeft>/g, '<LeftMargin>').replace(/<\/MarginLeft>/g, '</LeftMargin>')
1785
+ .replace(/<MarginRight>/g, '<RightMargin>').replace(/<\/MarginRight>/g, '</RightMargin>');
1786
+ console.error('[sanitizeReportXml] Fixed wrong margin element names (MarginX → XMargin) in embedded RDL');
1787
+ }
1788
+ // 10. Ensure <Body> inside embedded RDL <ReportSection> has <ReportItems /> as its first
1789
+ // child element. SSRS schema requires the order: ReportItems → Height → Style.
1790
+ // Without <ReportItems>, VS Designer can't surface the DataSet in the Report Data panel
1791
+ // (it appears as if the dataset "disappeared") and may refuse to open the report.
1792
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1793
+ // Match a <Body> that contains <Height> or <Style> but lacks <ReportItems>
1794
+ // (i.e., an empty skeleton body without any report items)
1795
+ const fixedRdl = rdl.replace(/<Body>\s*\n(\s*)((?!<ReportItems)[\s\S]*?)<\/Body>/, (_bodyMatch, indent, bodyContent) => {
1796
+ // Only add <ReportItems /> when the body has no report items at all
1797
+ if (bodyContent.includes('<ReportItems'))
1798
+ return _bodyMatch;
1799
+ console.error('[sanitizeReportXml] Added missing <ReportItems /> as first child of <Body> in embedded RDL');
1800
+ return `<Body>\n${indent}<ReportItems />\n${indent}${bodyContent.trimStart()}</Body>`;
1801
+ });
1802
+ if (fixedRdl === rdl)
1803
+ return _whole;
1804
+ return open + fixedRdl + close;
1805
+ });
1806
+ // 11. Fix doubled closing tags inside embedded RDL CDATA.
1807
+ // AI generators sometimes emit </Foo></Foo> (the closing tag twice).
1808
+ // These are invalid XML and cause "Deserialization failed" in VS Designer.
1809
+ // Pattern: </TagName></TagName> → </TagName>
1810
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1811
+ const fixedRdl = rdl.replace(/<\/(\w+)><\/\1>/g, (_m, tag) => {
1812
+ console.error(`[sanitizeReportXml] Removed doubled closing tag </${tag}></${tag}> in embedded RDL`);
1813
+ return `</${tag}>`;
1814
+ });
1815
+ if (fixedRdl === rdl)
1816
+ return _whole;
1817
+ return open + fixedRdl + close;
1818
+ });
1819
+ // 12. Fix <Value> as direct child of <Textbox> in embedded RDL.
1820
+ // SSRS 2008+ schema requires: <Textbox> → <Paragraphs><Paragraph><TextRuns><TextRun><Value>
1821
+ // AI generators sometimes emit <Value> directly inside <Textbox>, which causes:
1822
+ // "invalid child element 'Value'" error in VS Designer.
1823
+ // This fix wraps any bare <Value>…</Value> found as a direct child of <Textbox>
1824
+ // into the correct paragraph/textrun structure.
1825
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1826
+ // Look for <Textbox ...> that contains a direct <Value> child (not inside <TextRun>)
1827
+ const fixedRdl = rdl.replace(/(<Textbox\b[^>]*>)([\s\S]*?)(<\/Textbox>)/g, (tbMatch, tbOpen, tbContent, tbClose) => {
1828
+ // Only act if there is a <Value> but no <Paragraphs> wrapping yet
1829
+ if (!tbContent.includes('<Value>') && !tbContent.includes('<Value ='))
1830
+ return tbMatch;
1831
+ if (tbContent.includes('<Paragraphs>'))
1832
+ return tbMatch;
1833
+ const fixedContent = tbContent.replace(/<Value>([\s\S]*?)<\/Value>/, (_vMatch, val) => {
1834
+ console.error('[sanitizeReportXml] Wrapped bare <Value> in <Textbox> into <Paragraphs> structure');
1835
+ return `<Paragraphs>\n <Paragraph>\n <TextRuns>\n <TextRun>\n <Value>${val}</Value>\n <Style />\n </TextRun>\n </TextRuns>\n <Style />\n </Paragraph>\n </Paragraphs>`;
1836
+ });
1837
+ if (fixedContent === tbContent)
1838
+ return tbMatch;
1839
+ return tbOpen + fixedContent + tbClose;
1840
+ });
1841
+ if (fixedRdl === rdl)
1842
+ return _whole;
1843
+ return open + fixedRdl + close;
1844
+ });
1845
+ // 13. Fix <ColSpan>/<RowSpan> as direct children of <TablixCell>.
1846
+ // SSRS schema only allows CellContents, DataElementName, DataElementOutput
1847
+ // as direct children of <TablixCell>. ColSpan/RowSpan must be INSIDE
1848
+ // <CellContents> (after the report item, before </CellContents>).
1849
+ // AI generators emit them BEFORE or AFTER the <CellContents> block:
1850
+ // <TablixCell><ColSpan>2</ColSpan><CellContents>...</CellContents></TablixCell>
1851
+ // <TablixCell><CellContents>...</CellContents><ColSpan>2</ColSpan></TablixCell>
1852
+ // Both cause "invalid child element 'ColSpan'" deserialization error.
1853
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1854
+ const fixedRdl = rdl.replace(/(<TablixCell>)([\s\S]*?)(<\/TablixCell>)/g, (tcMatch, tcOpen, tcContent, tcClose) => {
1855
+ // Use indexOf to split tcContent into: beforeCC / ccBlock / afterCC.
1856
+ // This reliably handles spans placed either before OR after CellContents.
1857
+ const ccStart = tcContent.indexOf('<CellContents');
1858
+ const ccEnd = tcContent.indexOf('</CellContents>');
1859
+ if (ccStart === -1 || ccEnd === -1)
1860
+ return tcMatch;
1861
+ const beforeCC = tcContent.substring(0, ccStart);
1862
+ const ccBlock = tcContent.substring(ccStart, ccEnd + '</CellContents>'.length);
1863
+ const afterCC = tcContent.substring(ccEnd + '</CellContents>'.length);
1864
+ // Collect ColSpan/RowSpan from anywhere outside CellContents
1865
+ const spanTagRe = () => /[ \t\r\n]*<(ColSpan|RowSpan)>[^<]*<\/\1>/g;
1866
+ let spans = '';
1867
+ const cleanBefore = beforeCC.replace(spanTagRe(), (m) => { spans += '\n' + m.trim(); return ''; });
1868
+ const cleanAfter = afterCC.replace(spanTagRe(), (m) => { spans += '\n' + m.trim(); return ''; });
1869
+ if (!spans)
1870
+ return tcMatch;
1871
+ // Move collected spans inside CellContents, just before </CellContents>
1872
+ const fixedCC = ccBlock.replace('</CellContents>', `${spans}\n</CellContents>`);
1873
+ console.error('[sanitizeReportXml] Moved <ColSpan>/<RowSpan> from <TablixCell> into <CellContents> in embedded RDL');
1874
+ return tcOpen + cleanBefore + fixedCC + cleanAfter + tcClose;
1875
+ });
1876
+ if (fixedRdl === rdl)
1877
+ return _whole;
1878
+ return open + fixedRdl + close;
1879
+ });
1880
+ // 14. Fix flat border properties as direct children of <Style>.
1881
+ // SSRS <Style> only accepts <Border>, <TopBorder>, <BottomBorder>,
1882
+ // <LeftBorder>, <RightBorder> as border wrappers — not flat attributes
1883
+ // like <BorderStyle>, <BorderColor>, <BorderWidth>.
1884
+ // AI generators often emit:
1885
+ // <Style><BorderStyle>Solid</BorderStyle><BorderColor>#000</BorderColor></Style>
1886
+ // but the correct form is:
1887
+ // <Style><Border><Style>Solid</Style><Color>#000</Color></Border></Style>
1888
+ // Same pattern applies to TopBorderStyle/TopBorderColor/TopBorderWidth etc.
1889
+ //
1890
+ // Previous approach (matching <Style>…</Style> blocks non-greedily) had a
1891
+ // nesting failure: if an outer <Style> contained a nested <Style> element
1892
+ // (e.g. <Border><Style>Solid</Style>…</Border>) BEFORE a flat <BorderStyle>,
1893
+ // the non-greedy regex would bind the outer opening <Style> to the inner
1894
+ // closing </Style>, leaving the flat tag unprocessed.
1895
+ //
1896
+ // New approach: scan the CDATA directly for flat border-property clusters
1897
+ // and replace them with the correct wrapper, independent of the containing
1898
+ // <Style> block. Adjacent flat tags (same group, separated only by
1899
+ // whitespace) are collapsed into a single wrapper element.
1900
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/g, (_whole, open, rdl, close) => {
1901
+ let fixedRdl = rdl;
1902
+ let changed = false;
1903
+ const groups = [
1904
+ ['Border', 'Border'],
1905
+ ['TopBorder', 'TopBorder'],
1906
+ ['BottomBorder', 'BottomBorder'],
1907
+ ['LeftBorder', 'LeftBorder'],
1908
+ ['RightBorder', 'RightBorder'],
1909
+ ];
1910
+ for (const [prefix, wrapper] of groups) {
1911
+ const st = `${prefix}Style`;
1912
+ const ct = `${prefix}Color`;
1913
+ const wt = `${prefix}Width`;
1914
+ if (!new RegExp(`<(?:${st}|${ct}|${wt})>`).test(fixedRdl))
1915
+ continue;
1916
+ // Build a regex that matches a cluster of 1–3 adjacent flat border tags
1917
+ // for this group (in any order, with optional whitespace between them).
1918
+ const singleTag = `(?:<${st}>([^<]*)<\\/${st}>|<${ct}>([^<]*)<\\/${ct}>|<${wt}>([^<]*)<\\/${wt}>)`;
1919
+ const clusterRe = new RegExp(`${singleTag}(?:\\s*${singleTag})?(?:\\s*${singleTag})?`, 'g');
1920
+ fixedRdl = fixedRdl.replace(clusterRe, (match) => {
1921
+ // Extract each flat-tag value from the matched cluster via side-effect callbacks.
1922
+ let bStyle = '', bColor = '', bWidth = '';
1923
+ match.replace(new RegExp(`<${st}>([^<]*)<\\/${st}>`), (_, v) => { bStyle = v; return ''; });
1924
+ match.replace(new RegExp(`<${ct}>([^<]*)<\\/${ct}>`), (_, v) => { bColor = v; return ''; });
1925
+ match.replace(new RegExp(`<${wt}>([^<]*)<\\/${wt}>`), (_, v) => { bWidth = v; return ''; });
1926
+ let inner = '';
1927
+ if (bStyle)
1928
+ inner += `<Style>${bStyle}</Style>`;
1929
+ if (bColor)
1930
+ inner += `<Color>${bColor}</Color>`;
1931
+ if (bWidth)
1932
+ inner += `<Width>${bWidth}</Width>`;
1933
+ changed = true;
1934
+ return `<${wrapper}>${inner}</${wrapper}>`;
1935
+ });
1936
+ }
1937
+ if (!changed)
1938
+ return _whole;
1939
+ console.error('[sanitizeReportXml] Wrapped flat border properties into <Border> inside <Style> in embedded RDL');
1940
+ return open + fixedRdl + close;
1941
+ });
1942
+ // 15. Fix absorbed TablixCells following a ColSpan > 1.
1943
+ // Confirmed by real D365FO reports: when <CellContents> has <ColSpan>N</ColSpan>,
1944
+ // the next N-1 sibling TablixCells in the same <TablixCells> block must be empty
1945
+ // (<TablixCell />). AI generators give those absorbed cells full CellContents,
1946
+ // causing VS Report Designer to render an empty/broken design surface.
1947
+ // Approach: for each <ColSpan>N</ColSpan>, trace past its containing
1948
+ // </CellContents></TablixCell> then depth-count-walk the next N-1 TablixCells
1949
+ // and replace any non-empty ones with <TablixCell />.
1950
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
1951
+ let fixedRdl = rdl;
1952
+ const patches = [];
1953
+ const csRe = /<ColSpan>(\d+)<\/ColSpan>/g;
1954
+ let csMatch;
1955
+ while ((csMatch = csRe.exec(fixedRdl)) !== null) {
1956
+ const span = parseInt(csMatch[1], 10);
1957
+ if (span <= 1)
1958
+ continue;
1959
+ // Find </CellContents> that closes the CellContents containing this ColSpan
1960
+ const ccCloseIdx = fixedRdl.indexOf('</CellContents>', csMatch.index + csMatch[0].length);
1961
+ if (ccCloseIdx === -1)
1962
+ continue;
1963
+ // Find </TablixCell> that closes the TablixCell containing this CellContents
1964
+ const tcCloseIdx = fixedRdl.indexOf('</TablixCell>', ccCloseIdx + '</CellContents>'.length);
1965
+ if (tcCloseIdx === -1)
1966
+ continue;
1967
+ let pos = tcCloseIdx + '</TablixCell>'.length;
1968
+ for (let i = 0; i < span - 1; i++) {
1969
+ // Skip whitespace
1970
+ while (pos < fixedRdl.length && /\s/.test(fixedRdl[pos]))
1971
+ pos++;
1972
+ if (fixedRdl.startsWith('<TablixCell />', pos) || fixedRdl.startsWith('<TablixCell/>', pos)) {
1973
+ // Already empty — advance
1974
+ pos += fixedRdl.startsWith('<TablixCell />', pos) ? '<TablixCell />'.length : '<TablixCell/>'.length;
1975
+ continue;
1976
+ }
1977
+ if (!fixedRdl.startsWith('<TablixCell>', pos))
1978
+ break; // Not a TablixCell
1979
+ // Walk to balanced </TablixCell>, counting nested TablixCell depth
1980
+ let depth = 1;
1981
+ let search = pos + '<TablixCell>'.length;
1982
+ while (depth > 0 && search < fixedRdl.length) {
1983
+ const nextOpen = fixedRdl.indexOf('<TablixCell>', search);
1984
+ const nextClose = fixedRdl.indexOf('</TablixCell>', search);
1985
+ if (nextClose === -1) {
1986
+ depth = 0;
1987
+ break;
1988
+ }
1989
+ if (nextOpen !== -1 && nextOpen < nextClose) {
1990
+ depth++;
1991
+ search = nextOpen + '<TablixCell>'.length;
1992
+ }
1993
+ else {
1994
+ depth--;
1995
+ search = nextClose + '</TablixCell>'.length;
1996
+ }
1997
+ }
1998
+ const cellEnd = search;
1999
+ patches.push({ start: pos, end: cellEnd });
2000
+ pos = cellEnd;
2001
+ }
2002
+ }
2003
+ if (patches.length === 0)
2004
+ return _whole;
2005
+ // Apply patches in reverse order to preserve string positions
2006
+ patches.sort((a, b) => b.start - a.start);
2007
+ let result = fixedRdl;
2008
+ for (const p of patches) {
2009
+ result = result.substring(0, p.start) + '<TablixCell />' + result.substring(p.end);
2010
+ }
2011
+ console.error(`[sanitizeReportXml] Emptied ${patches.length} absorbed TablixCell(s) following ColSpan in embedded RDL`);
2012
+ return open + result + close;
2013
+ });
2014
+ // 16. Rename reversed border side wrapper element names.
2015
+ // SSRS schema expects <TopBorder>, <BottomBorder>, <LeftBorder>, <RightBorder>.
2016
+ // AI generators often emit them reversed: <BorderTop>, <BorderBottom>, etc.
2017
+ // The inner content (<Style>, <Color>, <Width>) is already correct — only
2018
+ // the wrapper element name needs to change.
2019
+ if (xml.includes('<BorderTop>') || xml.includes('<BorderBottom>') ||
2020
+ xml.includes('<BorderLeft>') || xml.includes('<BorderRight>')) {
2021
+ xml = xml
2022
+ .replace(/<BorderTop>/g, '<TopBorder>').replace(/<\/BorderTop>/g, '</TopBorder>')
2023
+ .replace(/<BorderBottom>/g, '<BottomBorder>').replace(/<\/BorderBottom>/g, '</BottomBorder>')
2024
+ .replace(/<BorderLeft>/g, '<LeftBorder>').replace(/<\/BorderLeft>/g, '</LeftBorder>')
2025
+ .replace(/<BorderRight>/g, '<RightBorder>').replace(/<\/BorderRight>/g, '</RightBorder>');
2026
+ console.error('[sanitizeReportXml] Fixed reversed border side wrapper names (BorderXxx → XxxBorder) in RDL');
2027
+ }
2028
+ // 17. Add missing </Style> before </Paragraph> when Paragraph-level Style is
2029
+ // left unclosed. AI generators sometimes emit:
2030
+ // <Paragraph><TextRuns>...</TextRuns><Style><TextAlign>Right</TextAlign></Paragraph>
2031
+ // The </Style> before </Paragraph> is missing, which makes the XML
2032
+ // malformed (</Paragraph> appears to close inside <Style>) and causes
2033
+ // SSRS deserialization to fail entirely.
2034
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/, (_whole, open, rdl, close) => {
2035
+ const fixedRdl = rdl.replace(/<Paragraph>([\s\S]*?)<\/Paragraph>/g, (match, inner) => {
2036
+ const opens = (inner.match(/<Style>/g) || []).length;
2037
+ const closes = (inner.match(/<\/Style>/g) || []).length;
2038
+ if (opens === closes)
2039
+ return match;
2040
+ const missing = opens - closes;
2041
+ console.error('[sanitizeReportXml] Added missing </Style> tag(s) inside <Paragraph> in embedded RDL');
2042
+ return `<Paragraph>${inner}${'</Style>'.repeat(missing)}</Paragraph>`;
2043
+ });
2044
+ if (fixedRdl === rdl)
2045
+ return _whole;
2046
+ return open + fixedRdl + close;
2047
+ });
2048
+ // 18. Reconcile TablixCells count with TablixColumns count.
2049
+ // Each TablixRow must have exactly as many TablixCell elements as there
2050
+ // are TablixColumn entries in the enclosing Tablix's TablixColumns block.
2051
+ // When the counts are mismatched VS Report Designer throws:
2052
+ // "Index was out of range. Must be non-negative and less than the size
2053
+ // of the collection. Parameter name: index"
2054
+ //
2055
+ // Two cases are handled:
2056
+ // A) A TablixRow has FEWER cells than TablixColumns → pad with <TablixCell />
2057
+ // B) TablixColumns has FEWER entries than max cells per row → pad columns
2058
+ //
2059
+ // The fix finds each top-level <Tablix>…</Tablix> block using depth
2060
+ // tracking (to handle multiple/nested Tablix controls correctly) and
2061
+ // reconciles column vs cell counts within each one independently.
2062
+ xml = xml.replace(/(<Text><!\[CDATA\[)([\s\S]*?)(\]\]><\/Text>)/g, (_whole, open, rdl, close) => {
2063
+ let fixedRdl = rdl;
2064
+ let changed = false;
2065
+ const processTablix = (block) => {
2066
+ // Count declared TablixColumn entries (exclude TablixColumns container)
2067
+ const colsMatch = block.match(/<TablixColumns>([\s\S]*?)<\/TablixColumns>/);
2068
+ if (!colsMatch)
2069
+ return block;
2070
+ const colCount = (colsMatch[1].match(/<TablixColumn[\s>\/]/g) || []).length;
2071
+ if (colCount === 0)
2072
+ return block;
2073
+ // Sub-case A: pad each TablixCells block that has too few cells
2074
+ let result = block.replace(/(<TablixCells>)([\s\S]*?)(<\/TablixCells>)/g, (m, o, inner, c) => {
2075
+ const n = (inner.match(/<TablixCell[\s>\/]/g) || []).length;
2076
+ if (n >= colCount)
2077
+ return m;
2078
+ changed = true;
2079
+ const padding = Array(colCount - n).fill('\t\t\t\t<TablixCell />').join('\n');
2080
+ return `${o}${inner}\n${padding}\n\t\t\t${c}`;
2081
+ });
2082
+ // Sub-case B: ensure TablixColumns has enough entries
2083
+ const cellsBlocks = result.match(/<TablixCells>([\s\S]*?)<\/TablixCells>/g) || [];
2084
+ const maxCells = cellsBlocks.reduce((mx, b) => {
2085
+ const n = (b.match(/<TablixCell[\s>\/]/g) || []).length;
2086
+ return n > mx ? n : mx;
2087
+ }, 0);
2088
+ if (maxCells > colCount) {
2089
+ const extra = Array(maxCells - colCount)
2090
+ .fill('\t\t<TablixColumn><Width>1in</Width></TablixColumn>')
2091
+ .join('\n');
2092
+ result = result.replace('</TablixColumns>', `\n${extra}\n\t\t</TablixColumns>`);
2093
+ changed = true;
2094
+ }
2095
+ return result;
2096
+ };
2097
+ // Depth-tracking scan for each top-level <Tablix>…</Tablix> block.
2098
+ // Child elements (<TablixBody>, <TablixColumns>, <TablixCell>, …) all have
2099
+ // letters immediately after '<Tablix', so the main element is identified by
2100
+ // '<Tablix' followed by '>', ' ', tab, or newline.
2101
+ let pos = 0;
2102
+ while (pos < fixedRdl.length) {
2103
+ const tagStart = fixedRdl.indexOf('<Tablix', pos);
2104
+ if (tagStart < 0)
2105
+ break;
2106
+ const ch = fixedRdl[tagStart + 7];
2107
+ if (ch !== '>' && ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r') {
2108
+ pos = tagStart + 8;
2109
+ continue;
2110
+ }
2111
+ // Self-closing <Tablix /> — skip
2112
+ const tagEndIdx = fixedRdl.indexOf('>', tagStart);
2113
+ if (tagEndIdx < 0)
2114
+ break;
2115
+ if (fixedRdl[tagEndIdx - 1] === '/') {
2116
+ pos = tagEndIdx + 1;
2117
+ continue;
2118
+ }
2119
+ // Depth-scan for matching </Tablix>
2120
+ let depth = 1;
2121
+ let scan = tagEndIdx + 1;
2122
+ let closeTagPos = -1;
2123
+ while (depth > 0 && scan < fixedRdl.length) {
2124
+ const nxtOpen = fixedRdl.indexOf('<Tablix', scan);
2125
+ const nxtClose = fixedRdl.indexOf('</Tablix>', scan);
2126
+ if (nxtClose < 0)
2127
+ break;
2128
+ if (nxtOpen >= 0 && nxtOpen < nxtClose) {
2129
+ const nc = fixedRdl[nxtOpen + 7];
2130
+ if (nc === '>' || nc === ' ' || nc === '\t' || nc === '\n' || nc === '\r') {
2131
+ depth++;
2132
+ const innerTagEnd = fixedRdl.indexOf('>', nxtOpen);
2133
+ scan = innerTagEnd >= 0 ? innerTagEnd + 1 : nxtOpen + 8;
2134
+ }
2135
+ else {
2136
+ scan = nxtOpen + 8;
2137
+ }
2138
+ }
2139
+ else {
2140
+ depth--;
2141
+ if (depth === 0)
2142
+ closeTagPos = nxtClose;
2143
+ scan = nxtClose + 9; // '</Tablix>'.length === 9
2144
+ }
2145
+ }
2146
+ if (closeTagPos < 0)
2147
+ break;
2148
+ const blockEnd = closeTagPos + 9;
2149
+ const block = fixedRdl.substring(tagStart, blockEnd);
2150
+ const fixed = processTablix(block);
2151
+ if (fixed !== block) {
2152
+ fixedRdl = fixedRdl.substring(0, tagStart) + fixed + fixedRdl.substring(blockEnd);
2153
+ pos = tagStart + fixed.length;
2154
+ }
2155
+ else {
2156
+ pos = blockEnd;
2157
+ }
2158
+ }
2159
+ if (!changed)
2160
+ return _whole;
2161
+ console.error('[sanitizeReportXml] Reconciled TablixCell count with TablixColumn count in embedded RDL');
2162
+ return open + fixedRdl + close;
2163
+ });
2164
+ return xml;
2165
+ }
2166
+ /**
2167
+ * Convert <Text><![CDATA[…RDL…]]></Text> to XML entity-encoded form.
2168
+ *
2169
+ * D365FO stores and expects the embedded RDL as entity-encoded text, not CDATA:
2170
+ * <Text>&lt;?xml version="1.0"?&gt;&lt;Report ...&gt;...&lt;/Report&gt;</Text>
2171
+ *
2172
+ * CDATA is valid XML and semantically equivalent, but the VS Designer metadata loader
2173
+ * does not render <Designs> correctly when the <Text> value uses CDATA — the design
2174
+ * appears empty even though no parse error is raised. Using entity encoding matches
2175
+ * what VS writes natively and fixes the empty-design issue.
2176
+ *
2177
+ * This is a SEPARATE method from sanitizeReportXml intentionally:
2178
+ * - sanitizeReportXml operates on CDATA form (efficient regex over raw XML text)
2179
+ * - encodeReportTextElement runs AFTER sanitize, just before writing to disk
2180
+ */
2181
+ static encodeReportTextElement(xml) {
2182
+ return xml.replace(/<Text><!\[CDATA\[([\s\S]*?)\]\]><\/Text>/g, (_match, rdlInner) => {
2183
+ const encoded = rdlInner
2184
+ .replace(/&/g, '&amp;') // must be first to avoid double-encoding
2185
+ .replace(/</g, '&lt;')
2186
+ .replace(/>/g, '&gt;');
2187
+ return `<Text>${encoded}</Text>`;
2188
+ });
2189
+ }
2190
+ /**
2191
+ * Generate AxEdt XML (Extended Data Type).
2192
+ * Default i:type is AxEdtString; override via properties.edtType.
2193
+ * Accepts either the full AxEdt* form or a plain base-type name
2194
+ * (string → AxEdtString, integer/int → AxEdtInt, int64 → AxEdtInt64,
2195
+ * real → AxEdtReal, date → AxEdtDate, datetime/utcdatetime → AxEdtUtcDateTime,
2196
+ * enum → AxEdtEnum, guid → AxEdtGuid, container → AxEdtContainer).
2197
+ */
2198
+ static generateAxEdtXml(name, properties) {
2199
+ const edtTypeRaw = properties?.edtType || 'AxEdtString';
2200
+ const edtTypeNormMap = {
2201
+ string: 'AxEdtString',
2202
+ integer: 'AxEdtInt',
2203
+ int: 'AxEdtInt',
2204
+ int64: 'AxEdtInt64',
2205
+ real: 'AxEdtReal',
2206
+ date: 'AxEdtDate',
2207
+ datetime: 'AxEdtUtcDateTime',
2208
+ utcdatetime: 'AxEdtUtcDateTime',
2209
+ enum: 'AxEdtEnum',
2210
+ guid: 'AxEdtGuid',
2211
+ container: 'AxEdtContainer',
2212
+ };
2213
+ const edtType = edtTypeNormMap[edtTypeRaw.toLowerCase()] ?? edtTypeRaw;
2214
+ const label = properties?.label || '@TODO:LabelId';
2215
+ const extends_ = properties?.extends ? `\n\t<Extends>${properties.extends}</Extends>` : '';
2216
+ const stringSize = edtType === 'AxEdtString'
2217
+ ? `\n\t<StringSize>${properties?.stringSize ?? 30}</StringSize>` : '';
2218
+ return `<?xml version="1.0" encoding="utf-8"?>
2219
+ <AxEdt xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns=""
2220
+ \ti:type="${edtType}">
2221
+ \t<Name>${name}</Name>
2222
+ \t<Label>${label}</Label>${extends_}
2223
+ \t<ArrayElements />
2224
+ \t<Relations />
2225
+ \t<TableReferences />${stringSize}
2226
+ </AxEdt>`;
2227
+ }
2228
+ /**
2229
+ * Generate a minimal extension XML for AxEdtExtension,
2230
+ * AxDataEntityViewExtension, AxMenuItemDisplayExtension, AxMenuItemActionExtension,
2231
+ * AxMenuItemOutputExtension.
2232
+ * Name convention: BaseObjectName.ExtensionName (e.g. CustTable.MyExtension)
2233
+ */
2234
+ static generateAxSimpleExtensionXml(rootElement, name) {
2235
+ return `<?xml version="1.0" encoding="utf-8"?>
2236
+ <${rootElement} xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2237
+ \t<Name>${name}</Name>
2238
+ \t<PropertyModifications />
2239
+ </${rootElement}>`;
2240
+ }
2241
+ /**
2242
+ * Generate AxEnumExtension XML.
2243
+ * Name convention: BaseEnumName.PrefixExtension
2244
+ *
2245
+ * Supported properties:
2246
+ * enumValues: Array<{ name, label?, value?, countryRegionCodes?, helpText? }>
2247
+ */
2248
+ static generateAxEnumExtensionXml(name, properties) {
2249
+ // Build <EnumValues> block
2250
+ const enumValueSpecs = Array.isArray(properties?.enumValues) ? properties.enumValues : [];
2251
+ let enumValuesXml;
2252
+ if (enumValueSpecs.length === 0) {
2253
+ enumValuesXml = '\t<EnumValues />';
2254
+ }
2255
+ else {
2256
+ enumValuesXml = '\t<EnumValues>';
2257
+ for (const v of enumValueSpecs) {
2258
+ enumValuesXml += `\n\t\t<AxEnumValue>`;
2259
+ enumValuesXml += `\n\t\t\t<Name>${v.name}</Name>`;
2260
+ if (v.countryRegionCodes)
2261
+ enumValuesXml += `\n\t\t\t<CountryRegionCodes>${v.countryRegionCodes}</CountryRegionCodes>`;
2262
+ if (v.label)
2263
+ enumValuesXml += `\n\t\t\t<Label>${v.label}</Label>`;
2264
+ if (v.helpText)
2265
+ enumValuesXml += `\n\t\t\t<HelpText>${v.helpText}</HelpText>`;
2266
+ if (v.value !== undefined && v.value !== 0)
2267
+ enumValuesXml += `\n\t\t\t<Value>${v.value}</Value>`;
2268
+ enumValuesXml += `\n\t\t</AxEnumValue>`;
2269
+ }
2270
+ enumValuesXml += '\n\t</EnumValues>';
2271
+ }
2272
+ return `<?xml version="1.0" encoding="utf-8"?>
2273
+ <AxEnumExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2274
+ \t<Name>${name}</Name>
2275
+ ${enumValuesXml}
2276
+ \t<PropertyModifications />
2277
+ \t<ValueModifications />
2278
+ </AxEnumExtension>`;
2279
+ }
2280
+ /**
2281
+ * Generate AxTableExtension XML.
2282
+ * Name convention: TableName.PrefixExtension
2283
+ *
2284
+ * Supported properties:
2285
+ * fields: Array<{ name, edt?, enumType?, label?, mandatory?, fieldType? }>
2286
+ * fieldGroups: Array<{ name, label?, fields?: string[] }>
2287
+ * fieldGroupExtensions: Array<{ name, fields: string[] }> — extend base-table field groups
2288
+ * indexes: Array<{ name, fields: Array<{fieldName, direction?}>, allowDuplicates?, alternateKey? }>
2289
+ * relations: Array<{ name, relatedTable, constraints: Array<{fieldName, relatedFieldName}>,
2290
+ * cardinality?, relatedTableCardinality?, relationshipType? }>
2291
+ */
2292
+ static generateAxTableExtensionXml(name, properties) {
2293
+ // ── Fields ───────────────────────────────────────────────────────────────
2294
+ const fieldSpecs = Array.isArray(properties?.fields) ? properties.fields : [];
2295
+ let fieldsXml;
2296
+ if (fieldSpecs.length === 0) {
2297
+ fieldsXml = '\t<Fields />';
2298
+ }
2299
+ else {
2300
+ fieldsXml = '\t<Fields>\n';
2301
+ for (const f of fieldSpecs) {
2302
+ const iType = f.fieldType ?? (f.enumType ? 'AxTableFieldEnum' : 'AxTableFieldString');
2303
+ fieldsXml += `\t\t<AxTableField xmlns=""\n\t\t\ti:type="${iType}">\n`;
2304
+ fieldsXml += `\t\t\t<Name>${f.name}</Name>\n`;
2305
+ if (f.edt)
2306
+ fieldsXml += `\t\t\t<ExtendedDataType>${f.edt}</ExtendedDataType>\n`;
2307
+ if (f.label)
2308
+ fieldsXml += `\t\t\t<Label>${f.label}</Label>\n`;
2309
+ if (f.mandatory)
2310
+ fieldsXml += `\t\t\t<Mandatory>Yes</Mandatory>\n`;
2311
+ if (f.enumType)
2312
+ fieldsXml += `\t\t\t<EnumType>${f.enumType}</EnumType>\n`;
2313
+ fieldsXml += `\t\t</AxTableField>\n`;
2314
+ }
2315
+ fieldsXml += '\t</Fields>';
2316
+ }
2317
+ // ── FieldGroups (new groups defined in this extension) ───────────────────
2318
+ const fgSpecs = Array.isArray(properties?.fieldGroups) ? properties.fieldGroups : [];
2319
+ let fieldGroupsXml;
2320
+ if (fgSpecs.length === 0) {
2321
+ fieldGroupsXml = '\t<FieldGroups />';
2322
+ }
2323
+ else {
2324
+ fieldGroupsXml = '\t<FieldGroups>\n';
2325
+ for (const fg of fgSpecs) {
2326
+ fieldGroupsXml += `\t\t<AxTableFieldGroup>\n\t\t\t<Name>${fg.name}</Name>\n`;
2327
+ if (fg.label)
2328
+ fieldGroupsXml += `\t\t\t<Label>${fg.label}</Label>\n`;
2329
+ const fgFields = Array.isArray(fg.fields) ? fg.fields : [];
2330
+ if (fgFields.length === 0) {
2331
+ fieldGroupsXml += `\t\t\t<Fields />\n`;
2332
+ }
2333
+ else {
2334
+ fieldGroupsXml += `\t\t\t<Fields>\n`;
2335
+ for (const df of fgFields) {
2336
+ fieldGroupsXml += `\t\t\t\t<AxTableFieldGroupField>\n\t\t\t\t\t<DataField>${df}</DataField>\n\t\t\t\t</AxTableFieldGroupField>\n`;
2337
+ }
2338
+ fieldGroupsXml += `\t\t\t</Fields>\n`;
2339
+ }
2340
+ fieldGroupsXml += `\t\t</AxTableFieldGroup>\n`;
2341
+ }
2342
+ fieldGroupsXml += '\t</FieldGroups>';
2343
+ }
2344
+ // ── FieldGroupExtensions (extend base-table field groups) ────────────────
2345
+ const fgeSpecs = Array.isArray(properties?.fieldGroupExtensions) ? properties.fieldGroupExtensions : [];
2346
+ let fieldGroupExtensionsXml;
2347
+ if (fgeSpecs.length === 0) {
2348
+ fieldGroupExtensionsXml = '\t<FieldGroupExtensions />';
2349
+ }
2350
+ else {
2351
+ fieldGroupExtensionsXml = '\t<FieldGroupExtensions>\n';
2352
+ for (const fge of fgeSpecs) {
2353
+ fieldGroupExtensionsXml += `\t\t<AxTableFieldGroupExtension>\n\t\t\t<Name>${fge.name}</Name>\n`;
2354
+ const fgeFields = Array.isArray(fge.fields) ? fge.fields : [];
2355
+ if (fgeFields.length === 0) {
2356
+ fieldGroupExtensionsXml += `\t\t\t<Fields />\n`;
2357
+ }
2358
+ else {
2359
+ fieldGroupExtensionsXml += `\t\t\t<Fields>\n`;
2360
+ for (const df of fgeFields) {
2361
+ fieldGroupExtensionsXml += `\t\t\t\t<AxTableFieldGroupField>\n\t\t\t\t\t<DataField>${df}</DataField>\n\t\t\t\t</AxTableFieldGroupField>\n`;
2362
+ }
2363
+ fieldGroupExtensionsXml += `\t\t\t</Fields>\n`;
2364
+ }
2365
+ fieldGroupExtensionsXml += `\t\t</AxTableFieldGroupExtension>\n`;
2366
+ }
2367
+ fieldGroupExtensionsXml += '\t</FieldGroupExtensions>';
2368
+ }
2369
+ // ── Indexes ──────────────────────────────────────────────────────────────
2370
+ const idxSpecs = Array.isArray(properties?.indexes) ? properties.indexes : [];
2371
+ let indexesXml;
2372
+ if (idxSpecs.length === 0) {
2373
+ indexesXml = '\t<Indexes />';
2374
+ }
2375
+ else {
2376
+ indexesXml = '\t<Indexes>\n';
2377
+ for (const idx of idxSpecs) {
2378
+ indexesXml += `\t\t<AxTableIndex>\n\t\t\t<Name>${idx.name}</Name>\n`;
2379
+ if (idx.allowDuplicates !== undefined)
2380
+ indexesXml += `\t\t\t<AllowDuplicates>${idx.allowDuplicates ? 'Yes' : 'No'}</AllowDuplicates>\n`;
2381
+ if (idx.alternateKey)
2382
+ indexesXml += `\t\t\t<AlternateKey>Yes</AlternateKey>\n`;
2383
+ const idxFields = Array.isArray(idx.fields) ? idx.fields : [];
2384
+ if (idxFields.length === 0) {
2385
+ indexesXml += `\t\t\t<Fields />\n`;
2386
+ }
2387
+ else {
2388
+ indexesXml += `\t\t\t<Fields>\n`;
2389
+ for (const f of idxFields) {
2390
+ indexesXml += `\t\t\t\t<AxTableIndexField>\n\t\t\t\t\t<DataField>${f.fieldName}</DataField>\n`;
2391
+ if (f.direction)
2392
+ indexesXml += `\t\t\t\t\t<Direction>${f.direction}</Direction>\n`;
2393
+ indexesXml += `\t\t\t\t</AxTableIndexField>\n`;
2394
+ }
2395
+ indexesXml += `\t\t\t</Fields>\n`;
2396
+ }
2397
+ indexesXml += `\t\t</AxTableIndex>\n`;
2398
+ }
2399
+ indexesXml += '\t</Indexes>';
2400
+ }
2401
+ // ── Relations ────────────────────────────────────────────────────────────
2402
+ const relSpecs = Array.isArray(properties?.relations) ? properties.relations : [];
2403
+ let relationsXml;
2404
+ if (relSpecs.length === 0) {
2405
+ relationsXml = '\t<Relations />';
2406
+ }
2407
+ else {
2408
+ relationsXml = '\t<Relations>\n';
2409
+ for (const rel of relSpecs) {
2410
+ relationsXml += `\t\t<AxTableRelation>\n`;
2411
+ relationsXml += `\t\t\t<Name>${rel.name}</Name>\n`;
2412
+ relationsXml += `\t\t\t<Cardinality>${rel.cardinality || 'ZeroMore'}</Cardinality>\n`;
2413
+ relationsXml += `\t\t\t<RelatedTable>${rel.relatedTable}</RelatedTable>\n`;
2414
+ relationsXml += `\t\t\t<RelatedTableCardinality>${rel.relatedTableCardinality || 'ExactlyOne'}</RelatedTableCardinality>\n`;
2415
+ relationsXml += `\t\t\t<RelationshipType>${rel.relationshipType || 'Association'}</RelationshipType>\n`;
2416
+ const constraints = Array.isArray(rel.constraints) ? rel.constraints : [];
2417
+ if (constraints.length === 0) {
2418
+ relationsXml += `\t\t\t<Constraints />\n`;
2419
+ }
2420
+ else {
2421
+ relationsXml += `\t\t\t<Constraints>\n`;
2422
+ for (const c of constraints) {
2423
+ relationsXml += `\t\t\t\t<AxTableRelationConstraint xmlns="" i:type="AxTableRelationConstraintField">\n`;
2424
+ relationsXml += `\t\t\t\t\t<Name>${c.fieldName}</Name>\n`;
2425
+ relationsXml += `\t\t\t\t\t<Field>${c.fieldName}</Field>\n`;
2426
+ relationsXml += `\t\t\t\t\t<RelatedField>${c.relatedFieldName}</RelatedField>\n`;
2427
+ relationsXml += `\t\t\t\t</AxTableRelationConstraint>\n`;
2428
+ }
2429
+ relationsXml += `\t\t\t</Constraints>\n`;
2430
+ }
2431
+ relationsXml += `\t\t</AxTableRelation>\n`;
2432
+ }
2433
+ relationsXml += '\t</Relations>';
2434
+ }
2435
+ return `<?xml version="1.0" encoding="utf-8"?>
2436
+ <AxTableExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2437
+ \t<Name>${name}</Name>
2438
+ ${fieldGroupExtensionsXml}
2439
+ ${fieldGroupsXml}
2440
+ \t<FieldModifications />
2441
+ ${fieldsXml}
2442
+ \t<FullTextIndexes />
2443
+ ${indexesXml}
2444
+ \t<Mappings />
2445
+ \t<PropertyModifications />
2446
+ \t<RelationExtensions />
2447
+ \t<RelationModifications />
2448
+ ${relationsXml}
2449
+ </AxTableExtension>`;
2450
+ }
2451
+ /**
2452
+ * Generate AxFormExtension XML.
2453
+ * Name convention: FormName.ExtensionName
2454
+ */
2455
+ static generateAxFormExtensionXml(name) {
2456
+ return `<?xml version="1.0" encoding="utf-8"?>
2457
+ <AxFormExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V6">
2458
+ \t<Name>${name}</Name>
2459
+ \t<ControlModifications />
2460
+ \t<Controls />
2461
+ \t<DataSourceModifications />
2462
+ \t<DataSourceReferences />
2463
+ \t<DataSources />
2464
+ \t<Parts />
2465
+ \t<PropertyModifications />
2466
+ </AxFormExtension>`;
2467
+ }
2468
+ /**
2469
+ * Generate AxSecurityPrivilege XML. Delegates to the shared builder so this
2470
+ * mirror and the one in generateD365Xml.ts cannot drift.
2471
+ * @see buildAxSecurityPrivilegeXml for the property contract and element order.
2472
+ */
2473
+ static generateAxSecurityPrivilegeXml(name, properties) {
2474
+ return buildAxSecurityPrivilegeXml(name, properties);
2475
+ }
2476
+ /**
2477
+ * Normalize a name list that may arrive as an array or a comma/semicolon/
2478
+ * newline-separated string (models pass either). Returns trimmed, non-empty names.
2479
+ */
2480
+ static normalizeNameList(value) {
2481
+ if (!value)
2482
+ return [];
2483
+ const arr = Array.isArray(value) ? value : String(value).split(/[,;\n]+/);
2484
+ return arr.map((s) => String(s).trim()).filter((s) => s.length > 0);
2485
+ }
2486
+ /**
2487
+ * Render a security reference container: a self-closing tag when empty, or the
2488
+ * wrapped child references (e.g. <AxSecurityRolePermissionSet><Name>…</Name></…>).
2489
+ */
2490
+ static securityRefContainer(container, childTag, names) {
2491
+ if (names.length === 0)
2492
+ return `\t<${container} />`;
2493
+ const children = names
2494
+ .map(n => `\t\t<${childTag}>\n\t\t\t<Name>${n}</Name>\n\t\t</${childTag}>`)
2495
+ .join('\n');
2496
+ return `\t<${container}>\n${children}\n\t</${container}>`;
2497
+ }
2498
+ /**
2499
+ * Generate AxSecurityDuty XML.
2500
+ * properties.privileges – privilege names to reference (array or comma-separated).
2501
+ */
2502
+ static generateAxSecurityDutyXml(name, properties) {
2503
+ const label = properties?.label || '@TODO:LabelId';
2504
+ const privileges = this.normalizeNameList(properties?.privileges);
2505
+ return `<?xml version="1.0" encoding="utf-8"?>
2506
+ <AxSecurityDuty xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2507
+ \t<Name>${name}</Name>
2508
+ \t<Label>${label}</Label>
2509
+ ${this.securityRefContainer('Privileges', 'AxSecurityRolePermissionSet', privileges)}
2510
+ </AxSecurityDuty>`;
2511
+ }
2512
+ /**
2513
+ * Generate AxSecurityRole XML.
2514
+ * properties.duties – duty names to reference (array or comma-separated).
2515
+ * properties.privileges – privilege names to reference directly on the role.
2516
+ */
2517
+ static generateAxSecurityRoleXml(name, properties) {
2518
+ const label = properties?.label || '@TODO:LabelId';
2519
+ const duties = this.normalizeNameList(properties?.duties);
2520
+ const privileges = this.normalizeNameList(properties?.privileges);
2521
+ return `<?xml version="1.0" encoding="utf-8"?>
2522
+ <AxSecurityRole xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2523
+ \t<Name>${name}</Name>
2524
+ \t<Label>${label}</Label>
2525
+ \t<DirectAccessPermissions />
2526
+ ${this.securityRefContainer('Duties', 'AxSecurityRoleDutyPermission', duties)}
2527
+ ${this.securityRefContainer('Privileges', 'AxSecurityRolePermissionSet', privileges)}
2528
+ \t<SubRoles />
2529
+ </AxSecurityRole>`;
2530
+ }
2531
+ /**
2532
+ * Generate AxSecurityDutyExtension XML — adds privileges to an EXISTING (often
2533
+ * Microsoft-owned) duty without overlaying it. Real Microsoft object type, e.g.
2534
+ * K:\...\ApplicationCommon\AxSecurityDutyExtension\BatchJobMaintain.ApplicationCommon.xml.
2535
+ * Name convention: "<BaseDuty>.<PrefixOrModel>Extension" (same dot-notation as
2536
+ * menu-extension / table-extension — see DOT_NOTATION_EXTENSION_TYPES).
2537
+ * properties.privileges – privilege names to add to the base duty (array or comma-separated).
2538
+ */
2539
+ static generateAxSecurityDutyExtensionXml(name, properties) {
2540
+ const privileges = this.normalizeNameList(properties?.privileges);
2541
+ return `<?xml version="1.0" encoding="utf-8"?>
2542
+ <AxSecurityDutyExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2543
+ \t<Name>${name}</Name>
2544
+ ${this.securityRefContainer('Privileges', 'AxSecurityPrivilegeReference', privileges)}
2545
+ \t<PropertyModifications />
2546
+ </AxSecurityDutyExtension>`;
2547
+ }
2548
+ /**
2549
+ * Generate AxSecurityRoleExtension XML — adds duties and/or privileges to an
2550
+ * EXISTING (often Microsoft-owned) role without overlaying it. Real Microsoft
2551
+ * object type, e.g. K:\...\ApplicationCommon\AxSecurityRoleExtension\SystemUser.ApplicationCommon.xml.
2552
+ * Name convention: "<BaseRole>.<PrefixOrModel>Extension".
2553
+ * properties.duties – duty names to add to the base role (array or comma-separated).
2554
+ * properties.privileges – privilege names to add directly to the base role.
2555
+ */
2556
+ static generateAxSecurityRoleExtensionXml(name, properties) {
2557
+ const duties = this.normalizeNameList(properties?.duties);
2558
+ const privileges = this.normalizeNameList(properties?.privileges);
2559
+ return `<?xml version="1.0" encoding="utf-8"?>
2560
+ <AxSecurityRoleExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
2561
+ \t<Name>${name}</Name>
2562
+ \t<DirectAccessPermissions />
2563
+ ${this.securityRefContainer('Duties', 'AxSecurityDutyReference', duties)}
2564
+ ${this.securityRefContainer('Privileges', 'AxSecurityPrivilegeReference', privileges)}
2565
+ \t<PropertyModifications />
2566
+ </AxSecurityRoleExtension>`;
2567
+ }
2568
+ /**
2569
+ * Generate BusinessEventsContract class XML (AxClass) for a Business Event.
2570
+ * The class extends BusinessEventsBase and includes a companion contract class.
2571
+ */
2572
+ static generateBusinessEventXml(name, properties) {
2573
+ const label = properties?.label || `@TODO:${name}Label`;
2574
+ const helpText = properties?.helpText || `@TODO:${name}HelpText`;
2575
+ const module = properties?.module || 'ModuleAxapta::Other';
2576
+ const contractName = `${name}Contract`;
2577
+ const source = `[BusinessEvents(classStr(${contractName}),
2578
+ '${name}',
2579
+ '${name}',
2580
+ ${module})]
2581
+ public final class ${name} extends BusinessEventsBase
2582
+ {
2583
+ private ${contractName} contract;
2584
+
2585
+ public static ${name} newFromContract(${contractName} _contract)
2586
+ {
2587
+ ${name} event = new ${name}();
2588
+ event.contract = _contract;
2589
+ return event;
2590
+ }
2591
+
2592
+ [Hookable(false)]
2593
+ public BusinessEventsContract buildContract()
2594
+ {
2595
+ return contract;
2596
+ }
2597
+ }
2598
+
2599
+ // ── Contract class ──────────────────────────────────────────────────────────
2600
+ [DataContractAttribute]
2601
+ public final class ${contractName} extends BusinessEventsContract
2602
+ {
2603
+ // TODO: add private fields and parmXxx() methods for the event payload
2604
+
2605
+ public static ${contractName} newDefault()
2606
+ {
2607
+ ${contractName} c = new ${contractName}();
2608
+ return c;
2609
+ }
2610
+ }`;
2611
+ return XmlTemplateGenerator.generateAxClassXml(name, source, { label, helpText });
2612
+ }
2613
+ /**
2614
+ * Generate Workspace Tile XML (AxTile).
2615
+ * Tiles appear in workspace panorama sections as KPI / navigation tiles.
2616
+ */
2617
+ static generateAxTileXml(name, properties) {
2618
+ const label = properties?.label || `@TODO:${name}Label`;
2619
+ const helpText = properties?.helpText || `@TODO:${name}HelpText`;
2620
+ const tileType = properties?.tileType || 'Count'; // Count | Link | Summary
2621
+ const menuItem = properties?.menuItem || '';
2622
+ const query = properties?.query || '';
2623
+ return `<?xml version="1.0" encoding="utf-8"?>
2624
+ <AxTile xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V6">
2625
+ \t<Name>${name}</Name>
2626
+ \t<Label>${label}</Label>
2627
+ \t<HelpText>${helpText}</HelpText>
2628
+ \t<TileType>${tileType}</TileType>${menuItem ? `\n\t<MenuItemName>${menuItem}</MenuItemName>\n\t<MenuItemType>Display</MenuItemType>` : ''}${query ? `\n\t<Query>${query}</Query>` : ''}
2629
+ \t<Size>Wide</Size>
2630
+ \t<RefreshFrequency>600</RefreshFrequency>
2631
+ </AxTile>`;
2632
+ }
2633
+ /**
2634
+ * Generate KPI XML (AxKPI).
2635
+ * KPIs appear in workspace summary sections.
2636
+ */
2637
+ static generateAxKpiXml(name, properties) {
2638
+ const label = properties?.label || `@TODO:${name}Label`;
2639
+ const helpText = properties?.helpText || `@TODO:${name}HelpText`;
2640
+ const measure = properties?.measure || '';
2641
+ const dimension = properties?.dimension || '';
2642
+ return `<?xml version="1.0" encoding="utf-8"?>
2643
+ <AxKPI xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V6">
2644
+ \t<Name>${name}</Name>
2645
+ \t<Label>${label}</Label>
2646
+ \t<HelpText>${helpText}</HelpText>${measure ? `\n\t<Measure>${measure}</Measure>` : ''}${dimension ? `\n\t<MeasureDimension>${dimension}</MeasureDimension>` : ''}
2647
+ \t<Goal>0</Goal>
2648
+ \t<GoalType>None</GoalType>
2649
+ \t<Trend>None</Trend>
2650
+ </AxKPI>`;
2651
+ }
2652
+ /**
2653
+ * Generate AxMenu XML.
2654
+ */
2655
+ static generateAxMenuXml(name, properties) {
2656
+ const label = properties?.label || '@TODO:LabelId';
2657
+ return `<?xml version="1.0" encoding="utf-8"?>
2658
+ <AxMenu xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V1">
2659
+ \t<Name>${name}</Name>
2660
+ \t<Label>${label}</Label>
2661
+ \t<Elements />
2662
+ </AxMenu>`;
2663
+ }
2664
+ /**
2665
+ * Generate AxMenuExtension XML.
2666
+ * Name convention: MenuName.ExtensionName
2667
+ */
2668
+ static generateAxMenuExtensionXml(name) {
2669
+ return `<?xml version="1.0" encoding="utf-8"?>
2670
+ <AxMenuExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V1">
2671
+ \t<Name>${name}</Name>
2672
+ \t<Customizations />
2673
+ \t<Elements />
2674
+ \t<MenuElementModifications />
2675
+ \t<PropertyModifications />
2676
+ </AxMenuExtension>`;
2677
+ }
2678
+ /**
2679
+ * Generate AxMenuItemDisplay / AxMenuItemAction / AxMenuItemOutput XML.
2680
+ *
2681
+ * AOT folder mapping:
2682
+ * menu-item-display → AxMenuItemDisplay (ObjectType: Form)
2683
+ * menu-item-action → AxMenuItemAction (ObjectType: Class)
2684
+ * menu-item-output → AxMenuItemOutput (ObjectType: Report)
2685
+ */
2686
+ static generateAxMenuItemXml(itemType, name, properties) {
2687
+ const elemName = itemType === 'menu-item-action' ? 'AxMenuItemAction'
2688
+ : itemType === 'menu-item-output' ? 'AxMenuItemOutput'
2689
+ : 'AxMenuItemDisplay';
2690
+ const targetObject = properties?.targetObject || properties?.object || name;
2691
+ const label = properties?.label || '@TODO:LabelId';
2692
+ // Determine ObjectType based on item type and explicit properties.
2693
+ // D365FO serializer rules (confirmed from real XML files):
2694
+ // - AxMenuItemAction: ObjectType is always "Class"; must be present.
2695
+ // - AxMenuItemDisplay: ObjectType is OMITTED when targeting a Form (default);
2696
+ // use "Class" only when explicitly set.
2697
+ // - AxMenuItemOutput: ObjectType is "Class" (controller) or "SSRSReport";
2698
+ // "Report" is NOT a valid value — real files use "SSRSReport".
2699
+ const explicitObjType = properties?.objectType || properties?.targetType;
2700
+ let objType;
2701
+ if (itemType === 'menu-item-action') {
2702
+ // Action always needs ObjectType; default to Class
2703
+ objType = explicitObjType || 'Class';
2704
+ }
2705
+ else if (itemType === 'menu-item-output') {
2706
+ // Output: Class (controller pattern) or SSRSReport; "Report" is invalid
2707
+ if (explicitObjType === 'Report') {
2708
+ objType = 'SSRSReport';
2709
+ }
2710
+ else {
2711
+ objType = explicitObjType || 'Class';
2712
+ }
2713
+ }
2714
+ else {
2715
+ // Display: omit ObjectType entirely when targeting a Form (the implicit default).
2716
+ // Include it only when caller explicitly requests "Class".
2717
+ if (explicitObjType && explicitObjType !== 'Form') {
2718
+ objType = explicitObjType;
2719
+ }
2720
+ // else leave objType undefined → element omitted
2721
+ }
2722
+ const objectTypeXml = objType ? `\n\t<ObjectType>${objType}</ObjectType>` : '';
2723
+ return `<?xml version="1.0" encoding="utf-8"?>
2724
+ <${elemName} xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Microsoft.Dynamics.AX.Metadata.V1">
2725
+ \t<Name>${name}</Name>
2726
+ \t<Label>${label}</Label>
2727
+ \t<Object>${targetObject}</Object>${objectTypeXml}
2728
+ </${elemName}>`;
2729
+ }
2730
+ /**
2731
+ * Ensure AxMenuItemAction/Display/Output XML always has the required namespace
2732
+ * attributes on the root element. D365FO metadata deserializer rejects the file
2733
+ * without both:
2734
+ * xmlns="Microsoft.Dynamics.AX.Metadata.V1"
2735
+ * xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
2736
+ *
2737
+ * Also fix invalid ObjectType values:
2738
+ * "Form" → remove element entirely (display items targeting a form should
2739
+ * omit ObjectType; D365FO has no ObjectType enum value "Form")
2740
+ * "Report" → "SSRSReport" (only valid values are Class / SSRSReport)
2741
+ */
2742
+ static sanitizeMenuItemXml(xml) {
2743
+ // 1. Ensure xmlns namespace attributes on root element
2744
+ xml = xml.replace(/<(AxMenuItem(?:Action|Display|Output))(\s[^>]*)?>/, (_match, tag, attrs) => {
2745
+ let a = attrs || '';
2746
+ if (!a.includes('xmlns="Microsoft.Dynamics.AX.Metadata.V1"')) {
2747
+ a += ' xmlns="Microsoft.Dynamics.AX.Metadata.V1"';
2748
+ }
2749
+ if (!a.includes('xmlns:i="')) {
2750
+ a = ` xmlns:i="http://www.w3.org/2001/XMLSchema-instance"` + a;
2751
+ }
2752
+ return `<${tag}${a}>`;
2753
+ });
2754
+ // 2. Fix invalid ObjectType value "Form" → remove element
2755
+ // Real AxMenuItemDisplay files targeting forms simply omit ObjectType.
2756
+ xml = xml.replace(/\s*<ObjectType>Form<\/ObjectType>/g, '');
2757
+ // 3. Fix invalid ObjectType value "Report" → "SSRSReport"
2758
+ xml = xml.replace(/<ObjectType>Report<\/ObjectType>/g, '<ObjectType>SSRSReport</ObjectType>');
2759
+ return xml;
2760
+ }
2761
+ }
2762
+ /**
2763
+ * Visual Studio Project (.rnrproj) Manipulator
2764
+ */
2765
+ export class ProjectFileManager {
2766
+ parser;
2767
+ builder;
2768
+ constructor() {
2769
+ this.parser = new Parser({
2770
+ explicitArray: false,
2771
+ mergeAttrs: false,
2772
+ trim: true,
2773
+ });
2774
+ this.builder = new Builder({
2775
+ xmldec: { version: '1.0', encoding: 'utf-8' },
2776
+ renderOpts: { pretty: true, indent: ' ' },
2777
+ });
2778
+ }
2779
+ /**
2780
+ * Get friendly display folder name for project (used in Folder Include and Link)
2781
+ * e.g. class → Classes, enum → Base Enums
2782
+ */
2783
+ getFolderName(objectType) {
2784
+ const folderMap = {
2785
+ class: 'Classes',
2786
+ 'class-extension': 'Classes',
2787
+ table: 'Tables',
2788
+ enum: 'Base Enums',
2789
+ form: 'Forms',
2790
+ query: 'Queries',
2791
+ view: 'Views',
2792
+ 'data-entity': 'Data Entities',
2793
+ 'table-extension': 'Table Extensions',
2794
+ 'form-extension': 'Form Extensions',
2795
+ 'data-entity-extension': 'Data Entity Extensions',
2796
+ report: 'Reports',
2797
+ 'menu-item-display': 'Menu Items Display',
2798
+ 'menu-item-action': 'Menu Items Action',
2799
+ 'menu-item-output': 'Menu Items Output',
2800
+ 'menu-item-display-extension': 'Menu Item Display Extensions',
2801
+ 'menu-item-action-extension': 'Menu Item Action Extensions',
2802
+ 'menu-item-output-extension': 'Menu Item Output Extensions',
2803
+ edt: 'Extended Data Types',
2804
+ 'edt-extension': 'EDT Extensions',
2805
+ 'enum-extension': 'Enum Extensions',
2806
+ menu: 'Menus',
2807
+ 'menu-extension': 'Menu Extensions',
2808
+ 'security-privilege': 'Security Privileges',
2809
+ 'security-duty': 'Security Duties',
2810
+ 'security-role': 'Security Roles',
2811
+ 'security-duty-extension': 'Security Duties',
2812
+ 'security-role-extension': 'Security Roles',
2813
+ 'business-event': 'Classes',
2814
+ tile: 'Tiles',
2815
+ kpi: 'KPIs',
2816
+ map: 'Maps',
2817
+ };
2818
+ return folderMap[objectType] || 'Classes';
2819
+ }
2820
+ /**
2821
+ * Get AOT folder prefix for Content Include path (no .xml extension)
2822
+ * e.g. class → AxClass, enum → AxEnum, data-entity → AxDataEntityView
2823
+ */
2824
+ getAxFolderPrefix(objectType) {
2825
+ const prefixMap = {
2826
+ class: 'AxClass',
2827
+ 'class-extension': 'AxClass',
2828
+ table: 'AxTable',
2829
+ enum: 'AxEnum',
2830
+ form: 'AxForm',
2831
+ query: 'AxQuery',
2832
+ view: 'AxView',
2833
+ 'data-entity': 'AxDataEntityView',
2834
+ 'table-extension': 'AxTableExtension',
2835
+ 'form-extension': 'AxFormExtension',
2836
+ 'data-entity-extension': 'AxDataEntityViewExtension',
2837
+ report: 'AxReport',
2838
+ 'menu-item-display': 'AxMenuItemDisplay',
2839
+ 'menu-item-action': 'AxMenuItemAction',
2840
+ 'menu-item-output': 'AxMenuItemOutput',
2841
+ 'menu-item-display-extension': 'AxMenuItemDisplayExtension',
2842
+ 'menu-item-action-extension': 'AxMenuItemActionExtension',
2843
+ 'menu-item-output-extension': 'AxMenuItemOutputExtension',
2844
+ edt: 'AxEdt',
2845
+ 'edt-extension': 'AxEdtExtension',
2846
+ 'enum-extension': 'AxEnumExtension',
2847
+ menu: 'AxMenu',
2848
+ 'menu-extension': 'AxMenuExtension',
2849
+ 'security-privilege': 'AxSecurityPrivilege',
2850
+ 'security-duty': 'AxSecurityDuty',
2851
+ 'security-role': 'AxSecurityRole',
2852
+ 'security-duty-extension': 'AxSecurityDutyExtension',
2853
+ 'security-role-extension': 'AxSecurityRoleExtension',
2854
+ 'business-event': 'AxClass',
2855
+ tile: 'AxTile',
2856
+ kpi: 'AxKPI',
2857
+ map: 'AxMap',
2858
+ };
2859
+ return prefixMap[objectType] || 'AxClass';
2860
+ }
2861
+ /**
2862
+ * Add file reference to Visual Studio project
2863
+ * D365FO projects use ABSOLUTE paths to XML files in PackagesLocalDirectory
2864
+ * Returns true if file was added, false if file already exists in project
2865
+ */
2866
+ async addToProject(projectPath, objectType, objectName, _absoluteXmlPath // kept for API compatibility
2867
+ ) {
2868
+ return withProjectFileLock(projectPath, () => this._addToProjectLocked(projectPath, objectType, objectName));
2869
+ }
2870
+ async _addToProjectLocked(projectPath, objectType, objectName) {
2871
+ // Read project file (with retry for transient VS file locks)
2872
+ let projectXml = '';
2873
+ for (let attempt = 0; attempt < 5; attempt++) {
2874
+ try {
2875
+ projectXml = await fs.readFile(projectPath, 'utf-8');
2876
+ break;
2877
+ }
2878
+ catch (err) {
2879
+ if ((err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES') && attempt < 4) {
2880
+ await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
2881
+ continue;
2882
+ }
2883
+ throw err;
2884
+ }
2885
+ }
2886
+ // Strip UTF-8 BOM if present (VS writes BOM; Node fs.readFile keeps it)
2887
+ let hadBom = false;
2888
+ if (projectXml.charCodeAt(0) === 0xFEFF) {
2889
+ projectXml = projectXml.slice(1);
2890
+ hadBom = true;
2891
+ }
2892
+ const project = await this.parser.parseStringPromise(projectXml);
2893
+ // Ensure project structure exists
2894
+ if (!project.Project) {
2895
+ throw new Error('Invalid .rnrproj file structure');
2896
+ }
2897
+ // Initialize ItemGroup if not exists — insert BEFORE Import elements
2898
+ // so MSBuild/VS sees items before targets (xml2js preserves JS key order)
2899
+ if (!project.Project.ItemGroup) {
2900
+ const { Import, ...rest } = project.Project;
2901
+ project.Project = { ...rest, ItemGroup: [{ Folder: [] }, { Content: [] }] };
2902
+ if (Import) {
2903
+ project.Project.Import = Import;
2904
+ }
2905
+ }
2906
+ // Convert to array if single ItemGroup
2907
+ if (!Array.isArray(project.Project.ItemGroup)) {
2908
+ project.Project.ItemGroup = [project.Project.ItemGroup];
2909
+ }
2910
+ // Find or create Folder ItemGroup
2911
+ let folderGroup = project.Project.ItemGroup.find((group) => group.Folder !== undefined);
2912
+ if (!folderGroup) {
2913
+ folderGroup = { Folder: [] };
2914
+ project.Project.ItemGroup.push(folderGroup);
2915
+ }
2916
+ // Find or create Content ItemGroup
2917
+ let contentGroup = project.Project.ItemGroup.find((group) => group.Content !== undefined);
2918
+ if (!contentGroup) {
2919
+ contentGroup = { Content: [] };
2920
+ project.Project.ItemGroup.push(contentGroup);
2921
+ }
2922
+ // Ensure arrays
2923
+ if (!Array.isArray(folderGroup.Folder)) {
2924
+ folderGroup.Folder = folderGroup.Folder ? [folderGroup.Folder] : [];
2925
+ }
2926
+ if (!Array.isArray(contentGroup.Content)) {
2927
+ contentGroup.Content = contentGroup.Content ? [contentGroup.Content] : [];
2928
+ }
2929
+ // Get folder names for project organization
2930
+ const displayFolderName = this.getFolderName(objectType);
2931
+ const axFolderPrefix = this.getAxFolderPrefix(objectType);
2932
+ // Add folder if not exists (uses friendly display name, e.g. "Classes\")
2933
+ const folderExists = folderGroup.Folder.some((folder) => folder.$ && folder.$.Include === `${displayFolderName}\\`);
2934
+ if (!folderExists) {
2935
+ folderGroup.Folder.push({
2936
+ $: { Include: `${displayFolderName}\\` },
2937
+ });
2938
+ }
2939
+ // D365FO .rnrproj standard:
2940
+ // Content Include = AxClass\ObjectName (Ax prefix, NO .xml extension)
2941
+ // Link = Classes\ObjectName (display name, NO .xml extension)
2942
+ const contentInclude = `${axFolderPrefix}\\${objectName}`;
2943
+ const linkPath = `${displayFolderName}\\${objectName}`;
2944
+ // Check if file already in project
2945
+ const fileExists = contentGroup.Content.some((content) => content.$ && content.$.Include === contentInclude);
2946
+ if (fileExists) {
2947
+ console.error(`[ProjectFileManager] File ${objectName} is already in the project - skipping`);
2948
+ return false; // File already exists in project
2949
+ }
2950
+ // Add file reference
2951
+ contentGroup.Content.push({
2952
+ $: { Include: contentInclude },
2953
+ SubType: 'Content',
2954
+ Name: objectName,
2955
+ Link: linkPath,
2956
+ });
2957
+ console.error(`[ProjectFileManager] Added file reference to project, Content items: ${contentGroup.Content.length}`);
2958
+ // Write back to project file (with retry for transient VS file locks)
2959
+ const updatedXml = this.builder.buildObject(project);
2960
+ // Restore UTF-8 BOM if the original file had one (VS 2022 writes .rnrproj with BOM)
2961
+ const output = hadBom ? '\uFEFF' + updatedXml : updatedXml;
2962
+ for (let attempt = 0; attempt < 5; attempt++) {
2963
+ try {
2964
+ await fs.writeFile(projectPath, output, 'utf-8');
2965
+ break;
2966
+ }
2967
+ catch (err) {
2968
+ if ((err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES') && attempt < 4) {
2969
+ await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
2970
+ continue;
2971
+ }
2972
+ throw err;
2973
+ }
2974
+ }
2975
+ console.error(`[ProjectFileManager] Project file saved successfully`);
2976
+ return true; // File successfully added
2977
+ }
2978
+ /**
2979
+ * Add label file entries to Visual Studio project.
2980
+ * Each language needs TWO entries:
2981
+ * 1. AxLabelFile descriptor: Include="AxLabelFile\{id}_{lang}" Link="Label Files\{id}_{lang}"
2982
+ * 2. LabelResources .label.txt: Include="{id}.{lang}.label.txt" DependentUpon="AxLabelFile\{id}_{lang}"
2983
+ * Both are added inside a single file-lock + parse/write cycle for efficiency.
2984
+ * Returns the list of descriptor names that were newly added.
2985
+ */
2986
+ async addLabelToProject(projectPath, labelFileId, languages) {
2987
+ return withProjectFileLock(projectPath, () => this._addLabelToProjectLocked(projectPath, labelFileId, languages));
2988
+ }
2989
+ async _addLabelToProjectLocked(projectPath, labelFileId, languages) {
2990
+ // Read project file (with retry for transient VS file locks)
2991
+ let projectXml = '';
2992
+ for (let attempt = 0; attempt < 5; attempt++) {
2993
+ try {
2994
+ projectXml = await fs.readFile(projectPath, 'utf-8');
2995
+ break;
2996
+ }
2997
+ catch (err) {
2998
+ if ((err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES') && attempt < 4) {
2999
+ await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
3000
+ continue;
3001
+ }
3002
+ throw err;
3003
+ }
3004
+ }
3005
+ let hadBom = false;
3006
+ if (projectXml.charCodeAt(0) === 0xFEFF) {
3007
+ projectXml = projectXml.slice(1);
3008
+ hadBom = true;
3009
+ }
3010
+ const project = await this.parser.parseStringPromise(projectXml);
3011
+ if (!project.Project)
3012
+ throw new Error('Invalid .rnrproj file structure');
3013
+ // Ensure ItemGroup structure
3014
+ if (!project.Project.ItemGroup) {
3015
+ const { Import, ...rest } = project.Project;
3016
+ project.Project = { ...rest, ItemGroup: [{ Folder: [] }, { Content: [] }] };
3017
+ if (Import)
3018
+ project.Project.Import = Import;
3019
+ }
3020
+ if (!Array.isArray(project.Project.ItemGroup)) {
3021
+ project.Project.ItemGroup = [project.Project.ItemGroup];
3022
+ }
3023
+ let folderGroup = project.Project.ItemGroup.find((g) => g.Folder !== undefined);
3024
+ if (!folderGroup) {
3025
+ folderGroup = { Folder: [] };
3026
+ project.Project.ItemGroup.push(folderGroup);
3027
+ }
3028
+ let contentGroup = project.Project.ItemGroup.find((g) => g.Content !== undefined);
3029
+ if (!contentGroup) {
3030
+ contentGroup = { Content: [] };
3031
+ project.Project.ItemGroup.push(contentGroup);
3032
+ }
3033
+ if (!Array.isArray(folderGroup.Folder))
3034
+ folderGroup.Folder = folderGroup.Folder ? [folderGroup.Folder] : [];
3035
+ if (!Array.isArray(contentGroup.Content))
3036
+ contentGroup.Content = contentGroup.Content ? [contentGroup.Content] : [];
3037
+ // Ensure "Label Files\" folder entry
3038
+ const folderExists = folderGroup.Folder.some((f) => f.$ && f.$.Include === 'Label Files\\');
3039
+ if (!folderExists) {
3040
+ folderGroup.Folder.push({ $: { Include: 'Label Files\\' } });
3041
+ }
3042
+ const added = [];
3043
+ let newEntries = 0;
3044
+ const existingIncludes = new Set(contentGroup.Content.map((c) => c.$?.Include).filter(Boolean));
3045
+ for (const lang of languages) {
3046
+ const descriptorName = `${labelFileId}_${lang}`;
3047
+ const descriptorInclude = `AxLabelFile\\${descriptorName}`;
3048
+ const resourceFileName = `${labelFileId}.${lang}.label.txt`;
3049
+ // 1. AxLabelFile descriptor entry
3050
+ if (!existingIncludes.has(descriptorInclude)) {
3051
+ contentGroup.Content.push({
3052
+ $: { Include: descriptorInclude },
3053
+ SubType: 'Content',
3054
+ Name: descriptorName,
3055
+ Link: `Label Files\\${descriptorName}`,
3056
+ });
3057
+ existingIncludes.add(descriptorInclude);
3058
+ added.push(descriptorName);
3059
+ newEntries++;
3060
+ }
3061
+ // 2. LabelResources .label.txt entry with DependentUpon
3062
+ if (!existingIncludes.has(resourceFileName)) {
3063
+ contentGroup.Content.push({
3064
+ $: { Include: resourceFileName },
3065
+ SubType: 'Content',
3066
+ Name: resourceFileName,
3067
+ DependentUpon: descriptorInclude,
3068
+ });
3069
+ existingIncludes.add(resourceFileName);
3070
+ newEntries++;
3071
+ }
3072
+ }
3073
+ if (newEntries === 0) {
3074
+ console.error(`[ProjectFileManager] All label entries already in project — skipping write`);
3075
+ return added;
3076
+ }
3077
+ // Write back
3078
+ const updatedXml = this.builder.buildObject(project);
3079
+ const output = hadBom ? '\uFEFF' + updatedXml : updatedXml;
3080
+ for (let attempt = 0; attempt < 5; attempt++) {
3081
+ try {
3082
+ await fs.writeFile(projectPath, output, 'utf-8');
3083
+ break;
3084
+ }
3085
+ catch (err) {
3086
+ if ((err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES') && attempt < 4) {
3087
+ await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
3088
+ continue;
3089
+ }
3090
+ throw err;
3091
+ }
3092
+ }
3093
+ console.error(`[ProjectFileManager] Added ${added.length} label descriptor(s) + resource entries to project`);
3094
+ return added;
3095
+ }
3096
+ /**
3097
+ * Extract ModelName from Visual Studio project file
3098
+ * Returns the actual model name from PropertyGroup/Model or PropertyGroup/ModelName
3099
+ */
3100
+ async extractModelName(projectPath) {
3101
+ try {
3102
+ console.error(`[ProjectFileManager] Extracting model name from: ${projectPath}`);
3103
+ // Read project file
3104
+ let projectXml = await fs.readFile(projectPath, 'utf-8');
3105
+ // Strip UTF-8 BOM if present
3106
+ if (projectXml.charCodeAt(0) === 0xFEFF) {
3107
+ projectXml = projectXml.slice(1);
3108
+ }
3109
+ const project = await this.parser.parseStringPromise(projectXml);
3110
+ // Look for PropertyGroup with Model or ModelName
3111
+ if (project.Project && project.Project.PropertyGroup) {
3112
+ const propertyGroups = Array.isArray(project.Project.PropertyGroup)
3113
+ ? project.Project.PropertyGroup
3114
+ : [project.Project.PropertyGroup];
3115
+ for (const group of propertyGroups) {
3116
+ // Try <Model> tag first (standard D365FO format)
3117
+ if (group.Model) {
3118
+ const modelName = group.Model;
3119
+ console.error(`[ProjectFileManager] Found Model in project: ${modelName}`);
3120
+ return modelName;
3121
+ }
3122
+ // Fallback to <ModelName> tag (alternative format)
3123
+ if (group.ModelName) {
3124
+ const modelName = group.ModelName;
3125
+ console.error(`[ProjectFileManager] Found ModelName in project: ${modelName}`);
3126
+ return modelName;
3127
+ }
3128
+ }
3129
+ }
3130
+ console.error(`[ProjectFileManager] No Model or ModelName found in project file`);
3131
+ return null;
3132
+ }
3133
+ catch (error) {
3134
+ console.error(`[ProjectFileManager] Error extracting model name:`, error);
3135
+ return null;
3136
+ }
3137
+ }
3138
+ }
3139
+ /**
3140
+ * Returns a BP warning string when a `label` property is raw text (not a @File:Id reference).
3141
+ * xppbp raises BPErrorLabelIsText for any object-level label that is not a label ID.
3142
+ * Use the `labels` tool to find or create a label ID before writing the object.
3143
+ */
3144
+ function rawLabelBpWarning(properties, objectName) {
3145
+ const label = properties?.label;
3146
+ if (typeof label === 'string' && label.length > 0 && !label.startsWith('@')) {
3147
+ return `\n\n⚠️ **BPErrorLabelIsText risk:** Label "${label}" is raw text, not a label ID.\n` +
3148
+ `xppbp will report BPErrorLabelIsText on ${objectName}.\n` +
3149
+ `Fix: call \`labels(action="search", text="${label}")\` to find an existing @LabelFile:Id,\n` +
3150
+ `or \`labels(action="create", ...)\` to create one, then re-create with the @reference.`;
3151
+ }
3152
+ return '';
3153
+ }
3154
+ /**
3155
+ * Create D365FO file handler function
3156
+ */
3157
+ export async function handleCreateD365File(request, context) {
3158
+ const args = CreateD365FileArgsSchema.parse(request.params.arguments);
3159
+ // Grounding enforcement: extension objects modify the behaviour of existing
3160
+ // code, so when GROUNDING_ENFORCE=true the model must prove (via prepare_change)
3161
+ // that it inspected the real object before writing the extension.
3162
+ if (args.objectType.endsWith('-extension')) {
3163
+ const groundingError = enforceGrounding(args.groundingToken, `d365fo_file(action="create", objectType="${args.objectType}", objectName="${args.objectName}")`, args.objectName);
3164
+ if (groundingError)
3165
+ return groundingError;
3166
+ }
3167
+ // Semantic reference gate: when GROUNDING_ENFORCE=true, every identifier in the
3168
+ // X++ source must be proven against the symbol index before it reaches disk.
3169
+ const referenceError = gateOnReferenceErrors(args.sourceCode, context?.symbolIndex, `d365fo_file(action="create", objectType="${args.objectType}", objectName="${args.objectName}")`);
3170
+ if (referenceError)
3171
+ return referenceError;
3172
+ try {
3173
+ // Step 1: Try to find and parse .rnrproj to get actual ModelName
3174
+ let actualModelName = args.modelName;
3175
+ let wasAutoExtracted = false;
3176
+ let projectPathToUse = args.projectPath;
3177
+ let solutionPathToUse = args.solutionPath;
3178
+ console.error(`[create_d365fo_file] Initial modelName: ${actualModelName}`);
3179
+ // If neither projectPath nor solutionPath provided, try to get from config or auto-detect
3180
+ if (!projectPathToUse && !solutionPathToUse) {
3181
+ const configManager = getConfigManager();
3182
+ // Try to auto-detect from workspace (async)
3183
+ projectPathToUse = await configManager.getProjectPath() || undefined;
3184
+ solutionPathToUse = await configManager.getSolutionPath() || undefined;
3185
+ // If model name was not passed as argument, try to resolve from mcp.json config
3186
+ if (!actualModelName) {
3187
+ actualModelName = configManager.getModelName() ?? undefined;
3188
+ if (actualModelName) {
3189
+ const ctx = configManager.getContext();
3190
+ const source = ctx?.modelName ? 'modelName (mcp.json)' : 'workspacePath (mcp.json)';
3191
+ console.error(`[create_d365fo_file] Using modelName from ${source}: ${actualModelName}`);
3192
+ }
3193
+ }
3194
+ if (projectPathToUse) {
3195
+ console.error(`[create_d365fo_file] Using projectPath (auto-detected or from .mcp.json): ${projectPathToUse}`);
3196
+ }
3197
+ else if (solutionPathToUse) {
3198
+ console.error(`[create_d365fo_file] Using solutionPath (auto-detected or from .mcp.json): ${solutionPathToUse}`);
3199
+ }
3200
+ }
3201
+ // If projectPath is available, extract model name from it
3202
+ if (projectPathToUse) {
3203
+ const projectManager = new ProjectFileManager();
3204
+ const extractedModelName = await projectManager.extractModelName(projectPathToUse);
3205
+ if (extractedModelName) {
3206
+ actualModelName = extractedModelName;
3207
+ wasAutoExtracted = true;
3208
+ console.error(`[create_d365fo_file] Extracted ModelName from projectPath: ${actualModelName}`);
3209
+ // ✨ Register extracted model as custom (since it came from user's project)
3210
+ registerCustomModel(actualModelName);
3211
+ }
3212
+ }
3213
+ // If solutionPath is available, try to find .rnrproj and extract model name
3214
+ else if (solutionPathToUse) {
3215
+ const foundProjectPath = await ProjectFileFinder.findProjectInSolution(solutionPathToUse, actualModelName ?? '');
3216
+ if (foundProjectPath) {
3217
+ const projectManager = new ProjectFileManager();
3218
+ const extractedModelName = await projectManager.extractModelName(foundProjectPath);
3219
+ if (extractedModelName) {
3220
+ actualModelName = extractedModelName;
3221
+ wasAutoExtracted = true;
3222
+ console.error(`[create_d365fo_file] Extracted ModelName from solutionPath .rnrproj: ${actualModelName}`);
3223
+ // ✨ Register extracted model as custom (since it came from user's project)
3224
+ registerCustomModel(actualModelName);
3225
+ }
3226
+ }
3227
+ }
3228
+ // ⚠️ CRITICAL: modelName is required — must come from args, mcp.json, or .rnrproj extraction
3229
+ if (!actualModelName) {
3230
+ const errorMsg = '❌ ERROR: modelName could not be resolved.\n\n' +
3231
+ 'Provide it in one of these ways:\n' +
3232
+ ' 1. Pass modelName explicitly in the tool call arguments\n' +
3233
+ ' 2. Add modelName to .mcp.json context: { "context": { "modelName": "YourModel" } }\n' +
3234
+ ' 3. Add workspacePath ending with the package/model name: { "context": { "workspacePath": "C:\\\\AosService\\\\PackagesLocalDirectory\\\\YourModel" } }\n' +
3235
+ ' 4. Add projectPath or solutionPath to .mcp.json so the model is auto-extracted from .rnrproj';
3236
+ console.error(`[create_d365fo_file] ${errorMsg}`);
3237
+ return { content: [{ type: 'text', text: errorMsg }], isError: true };
3238
+ }
3239
+ // ⚠️ CRITICAL WARNING: If no project/solution path available anywhere
3240
+ if (!projectPathToUse && !solutionPathToUse) {
3241
+ console.error(`[create_d365fo_file] ⚠️ WARNING: No projectPath or solutionPath available (not in args, not in .mcp.json)!`);
3242
+ console.error(`[create_d365fo_file] ⚠️ Using modelName AS-IS: "${actualModelName}"`);
3243
+ console.error(`[create_d365fo_file] ⚠️ If "${actualModelName}" is a Microsoft model (e.g., ApplicationSuite), this will create the file in the WRONG location!`);
3244
+ console.error(`[create_d365fo_file] ⚠️ Add projectPath or solutionPath to .mcp.json config to auto-extract correct ModelName from .rnrproj!`);
3245
+ // Extra validation: Check for suspicious/placeholder model names
3246
+ const suspiciousNames = ['auto', 'test', 'example', 'temp', 'undefined', 'null'];
3247
+ // Known Microsoft standard D365FO models — NEVER use for custom code
3248
+ const knownMicrosoftModels = [
3249
+ 'applicationsuite', 'applicationcommon', 'applicationfoundation', 'applicationplatform',
3250
+ 'applicationwebcomponents', 'applicationworkspaces', 'foundation',
3251
+ 'directory', 'dimensions', 'currency', 'calendar', 'casemanagement',
3252
+ 'contactperson', 'datasharing', 'dataupgrade', 'datamaintenance',
3253
+ 'electronicreporting', 'electronicreportingcore',
3254
+ 'banktype', 'banktypes', 'benefitsmanagement', 'creditmanagement',
3255
+ ];
3256
+ const modelLower = actualModelName.toLowerCase();
3257
+ const isPlaceholder = suspiciousNames.includes(modelLower);
3258
+ const isMicrosoftModel = knownMicrosoftModels.includes(modelLower);
3259
+ if (isPlaceholder || isMicrosoftModel) {
3260
+ const reason = isPlaceholder
3261
+ ? `"${actualModelName}" is a placeholder value, not a real D365FO model`
3262
+ : `"${actualModelName}" is a Microsoft standard model — custom code must NEVER be created there`;
3263
+ const errorMsg = `❌ ERROR: ${reason}\n\n` +
3264
+ `Root cause: No projectPath or solutionPath was found (not in tool args, not in .mcp.json config).\n` +
3265
+ `Without projectPath, the tool uses the modelName parameter AS-IS, which is wrong.\n\n` +
3266
+ `To fix — add projectPath to .mcp.json (in the MCP server directory)::\n` +
3267
+ ` {\n` +
3268
+ ` "servers": {\n` +
3269
+ ` "context": {\n` +
3270
+ ` "projectPath": "C:\\\\VSProjects\\\\YourSolution\\\\YourProject\\\\YourProject.rnrproj",\n` +
3271
+ ` "solutionPath": "C:\\\\VSProjects\\\\YourSolution",\n` +
3272
+ ` "packagePath": "C:\\\\AosService\\\\PackagesLocalDirectory"\n` +
3273
+ ` }\n` +
3274
+ ` }\n` +
3275
+ ` }\n\n` +
3276
+ `Or pass projectPath explicitly in the tool call arguments.`;
3277
+ console.error(`[create_d365fo_file] ${errorMsg}`);
3278
+ return {
3279
+ content: [
3280
+ {
3281
+ type: 'text',
3282
+ text: errorMsg
3283
+ }
3284
+ ],
3285
+ isError: true,
3286
+ };
3287
+ }
3288
+ }
3289
+ console.error(`[create_d365fo_file] Final ModelName to use: ${actualModelName}${wasAutoExtracted ? ' (auto-extracted ✓)' : ' (as-is, NOT auto-extracted ⚠️)'}`);
3290
+ // Guard: refuse to create objects in generic placeholder model names.
3291
+ // These are never real D365FO models — if the AI reaches this point with a placeholder,
3292
+ // the workspace was not detected correctly and the file would land in the wrong location.
3293
+ const PLACEHOLDER_MODELS = new Set([
3294
+ 'mymodel', 'mypackage', 'model', 'package', 'modelname', 'packagename',
3295
+ 'yourmodel', 'yourpackage', 'custommodel', 'custompackage',
3296
+ 'testmodel', 'testpackage', 'samplemodel', 'samplepackage',
3297
+ ]);
3298
+ if (actualModelName && PLACEHOLDER_MODELS.has(actualModelName.toLowerCase())) {
3299
+ return {
3300
+ content: [
3301
+ {
3302
+ type: 'text',
3303
+ text: `❌ Model name "${actualModelName}" looks like a placeholder — file creation aborted.\n\n` +
3304
+ `The workspace / project path was not detected correctly, so the model name\n` +
3305
+ `could not be resolved from the .rnrproj file.\n\n` +
3306
+ `To fix this, provide one of:\n` +
3307
+ ` • projectPath — full path to the .rnrproj file (e.g. K:\\...\\MyProject.rnrproj)\n` +
3308
+ ` • solutionPath — directory containing the .rnrproj\n` +
3309
+ ` • A correct modelName that matches an actual D365FO model on disk\n\n` +
3310
+ `Never use "MyModel", "MyPackage" or similar placeholders as modelName.`,
3311
+ },
3312
+ ],
3313
+ isError: true,
3314
+ };
3315
+ }
3316
+ // Apply extension prefix to object name
3317
+ const objectPrefix = resolveObjectPrefix(actualModelName);
3318
+ const namingStyle = getExtensionNamingStyle();
3319
+ // If EXTENSION_PREFIX differs from modelName, the AI may have embedded the modelName
3320
+ // in the extension name. Strip it so applyObjectPrefix injects the correct prefix only.
3321
+ // NOTE: this stripping only makes sense for the prefix-infix style. Under the
3322
+ // model-name style the model name IS the desired token, so the stripping below is
3323
+ // skipped and applyObjectPrefix (given actualModelName) normalises the name instead.
3324
+ let effectiveObjectName = args.objectName;
3325
+ // Case A: dot-notation extension elements (table/form/EDT/enum extensions)
3326
+ // e.g. "CustTable.MyModelExtension" with modelName="MyModel" → "CustTable.Extension"
3327
+ // applyObjectPrefix then produces "CustTable.MyExtension"
3328
+ if (namingStyle !== 'model-name' &&
3329
+ args.objectName.includes('.') &&
3330
+ args.objectName.toLowerCase().endsWith('extension') &&
3331
+ actualModelName &&
3332
+ objectPrefix.toLowerCase() !== actualModelName.toLowerCase()) {
3333
+ const dotIdx = args.objectName.lastIndexOf('.');
3334
+ const basePart = args.objectName.slice(0, dotIdx);
3335
+ const suffixPart = args.objectName.slice(dotIdx + 1);
3336
+ if (suffixPart.toLowerCase().startsWith(actualModelName.toLowerCase())) {
3337
+ effectiveObjectName = `${basePart}.${suffixPart.slice(actualModelName.length)}`;
3338
+ console.error(`[create_d365fo_file] Stripped model name from dot-notation extension: ` +
3339
+ `${args.objectName} → ${effectiveObjectName}`);
3340
+ }
3341
+ }
3342
+ // Case B: extension classes (objectName ends with "_Extension")
3343
+ // e.g. "SalesFormLetterContoso_Extension" with modelName="ContosoExt" → "SalesFormLetter_Extension"
3344
+ // applyObjectPrefix then produces "SalesFormLetterContoso_Extension"
3345
+ if (namingStyle !== 'model-name' &&
3346
+ args.objectName.endsWith('_Extension') &&
3347
+ actualModelName &&
3348
+ objectPrefix.toLowerCase() !== actualModelName.toLowerCase()) {
3349
+ const baseName = args.objectName.slice(0, -'_Extension'.length);
3350
+ if (baseName.toLowerCase().endsWith(actualModelName.toLowerCase())) {
3351
+ effectiveObjectName = baseName.slice(0, -actualModelName.length) + '_Extension';
3352
+ console.error(`[create_d365fo_file] Stripped model name infix "${actualModelName}" from extension class: ` +
3353
+ `${args.objectName} → ${effectiveObjectName}`);
3354
+ }
3355
+ }
3356
+ // Case C: dot-notation extension types provided without a dot (bare base name)
3357
+ // e.g. objectType="table-extension", objectName="PurchTable"
3358
+ // → effectiveObjectName="PurchTable.Extension" so applyObjectPrefix SPECIAL CASE A
3359
+ // produces the correct "PurchTable.ContosoExtension" instead of falling into NORMAL CASE
3360
+ // and producing the wrong "ContosoPurchTable".
3361
+ const DOT_NOTATION_EXTENSION_TYPES = new Set([
3362
+ 'table-extension', 'form-extension', 'enum-extension', 'edt-extension',
3363
+ 'data-entity-extension', 'menu-item-display-extension', 'menu-item-action-extension',
3364
+ 'menu-item-output-extension', 'menu-extension',
3365
+ 'security-duty-extension', 'security-role-extension',
3366
+ ]);
3367
+ if (DOT_NOTATION_EXTENSION_TYPES.has(args.objectType) && !effectiveObjectName.includes('.')) {
3368
+ effectiveObjectName = `${effectiveObjectName}.Extension`;
3369
+ console.error(`[create_d365fo_file] Bare extension name auto-converted to dot-notation: ` +
3370
+ `${args.objectName} → ${effectiveObjectName}`);
3371
+ }
3372
+ // Case D: class extensions (CoC) provided as a bare base class name, i.e. WITHOUT
3373
+ // the "_Extension" suffix.
3374
+ // e.g. objectType="class-extension", objectName="SalesFormLetter"
3375
+ // → effectiveObjectName="SalesFormLetter_Extension" so applyObjectPrefix's
3376
+ // extension-class branch produces the correct name for the active style:
3377
+ // prefix style → SalesFormLetterCr_Extension
3378
+ // model-name style → SalesFormLetter_ContosoRobotics_Extension
3379
+ //
3380
+ // Without this, a bare base name has no dot and does not end in "_Extension", so it
3381
+ // falls into applyObjectPrefix's NORMAL CASE and is treated as a brand-new object —
3382
+ // wrongly producing "CrSalesFormLetter". This mirrors the dot-notation Case C above;
3383
+ // class-extension was the only extension type missing bare-name normalisation
3384
+ // (the EXTENSION_NAMING_STYLE work added the model-name branches but assumed the
3385
+ // caller always supplies the "_Extension" form for CoC classes).
3386
+ if (args.objectType === 'class-extension' && !effectiveObjectName.endsWith('_Extension')) {
3387
+ effectiveObjectName = `${effectiveObjectName}_Extension`;
3388
+ console.error(`[create_d365fo_file] Bare class-extension name auto-converted to _Extension form: ` +
3389
+ `${args.objectName} → ${effectiveObjectName}`);
3390
+ }
3391
+ // Pass actualModelName so the model-name naming style can use it as the extension
3392
+ // token. For the default prefix style (or non-extension objects) it is ignored.
3393
+ let finalObjectName = applyObjectPrefix(effectiveObjectName, objectPrefix, actualModelName);
3394
+ // Trailing suffix (EXTENSION_SUFFIX) applies to NEW objects only — never to
3395
+ // extension elements/classes. (For the prefix style applyObjectSuffix already
3396
+ // skips _Extension and dot-notation "…Extension" names; this guard additionally
3397
+ // covers the model-name style's "Base.ModelName" form, which has no "Extension"
3398
+ // word and would otherwise wrongly receive the suffix.)
3399
+ const isExtensionObjectType = args.objectType === 'class-extension' || DOT_NOTATION_EXTENSION_TYPES.has(args.objectType);
3400
+ const objectSuffix = getObjectSuffix();
3401
+ if (!isExtensionObjectType) {
3402
+ finalObjectName = applyObjectSuffix(finalObjectName, objectSuffix);
3403
+ }
3404
+ if (finalObjectName !== args.objectName) {
3405
+ console.error(`[create_d365fo_file] Applied naming: ${args.objectName} → ${finalObjectName}`);
3406
+ }
3407
+ // Determine object folder based on type
3408
+ const objectFolderMap = {
3409
+ class: 'AxClass',
3410
+ 'class-extension': 'AxClass',
3411
+ table: 'AxTable',
3412
+ enum: 'AxEnum',
3413
+ form: 'AxForm',
3414
+ query: 'AxQuery',
3415
+ view: 'AxView',
3416
+ 'data-entity': 'AxDataEntityView',
3417
+ report: 'AxReport',
3418
+ edt: 'AxEdt',
3419
+ 'edt-extension': 'AxEdtExtension',
3420
+ 'table-extension': 'AxTableExtension',
3421
+ 'form-extension': 'AxFormExtension',
3422
+ 'data-entity-extension': 'AxDataEntityViewExtension',
3423
+ 'enum-extension': 'AxEnumExtension',
3424
+ 'menu-item-display': 'AxMenuItemDisplay',
3425
+ 'menu-item-action': 'AxMenuItemAction',
3426
+ 'menu-item-output': 'AxMenuItemOutput',
3427
+ 'menu-item-display-extension': 'AxMenuItemDisplayExtension',
3428
+ 'menu-item-action-extension': 'AxMenuItemActionExtension',
3429
+ 'menu-item-output-extension': 'AxMenuItemOutputExtension',
3430
+ menu: 'AxMenu',
3431
+ 'menu-extension': 'AxMenuExtension',
3432
+ 'security-privilege': 'AxSecurityPrivilege',
3433
+ 'security-duty': 'AxSecurityDuty',
3434
+ 'security-role': 'AxSecurityRole',
3435
+ 'security-duty-extension': 'AxSecurityDutyExtension',
3436
+ 'security-role-extension': 'AxSecurityRoleExtension',
3437
+ 'business-event': 'AxClass',
3438
+ tile: 'AxTile',
3439
+ kpi: 'AxKPI',
3440
+ map: 'AxMap',
3441
+ };
3442
+ const objectFolder = objectFolderMap[args.objectType];
3443
+ if (!objectFolder) {
3444
+ throw new Error(`Unsupported object type: ${args.objectType}`);
3445
+ }
3446
+ // Construct full path - resolve package name
3447
+ // Package name can differ from model name in any environment (not just UDE).
3448
+ const configManager = getConfigManager();
3449
+ const configPackagePath = configManager.getPackagePath();
3450
+ const envType = await configManager.getDevEnvironmentType();
3451
+ let basePath;
3452
+ let resolvedPackageName;
3453
+ // Resolve the custom write root (D365FO_CUSTOM_PACKAGES_PATH).
3454
+ // Applies in both UDE and traditional mode — it always points to the repo
3455
+ // working tree where custom model XML lives, regardless of dev env type.
3456
+ const customWritePath = await configManager.getCustomPackagesPath();
3457
+ if (args.packageName) {
3458
+ // Explicit packageName always wins, regardless of environment type
3459
+ resolvedPackageName = args.packageName;
3460
+ // Custom write root beats the MS PLD for explicit packageName calls too.
3461
+ basePath = args.packagePath || customWritePath || configPackagePath || fallbackPackagePath();
3462
+ }
3463
+ else if (envType === 'ude') {
3464
+ // UDE mode: auto-resolve package name via descriptor scan across both roots
3465
+ const msPath = await configManager.getMicrosoftPackagesPath();
3466
+ const roots = [customWritePath, msPath].filter(Boolean);
3467
+ const resolver = new PackageResolver(roots);
3468
+ const resolved = await resolver.resolve(actualModelName);
3469
+ if (resolved) {
3470
+ resolvedPackageName = resolved.packageName;
3471
+ basePath = resolved.rootPath;
3472
+ }
3473
+ else {
3474
+ // Fallback: assume package == model (common case)
3475
+ resolvedPackageName = actualModelName;
3476
+ basePath = customWritePath || args.packagePath || configPackagePath || fallbackPackagePath();
3477
+ }
3478
+ }
3479
+ else {
3480
+ // Traditional mode: try descriptor-based resolution first so a package
3481
+ // whose name differs from the model name (e.g. package "ISVPackage",
3482
+ // model "ISV Package") resolves correctly without an explicit packageName
3483
+ // arg. Scan both the custom write root and D365FO_PACKAGE_PATH for the
3484
+ // matching descriptor; fall back to assuming package == model otherwise.
3485
+ const roots = [customWritePath, configPackagePath].filter(Boolean);
3486
+ const resolver = new PackageResolver(roots);
3487
+ const resolved = roots.length ? await resolver.resolve(actualModelName) : null;
3488
+ if (resolved) {
3489
+ resolvedPackageName = resolved.packageName;
3490
+ basePath = resolved.rootPath;
3491
+ }
3492
+ else {
3493
+ // Fallback: assume package == model.
3494
+ // Prefer the custom write root over D365FO_PACKAGE_PATH so custom model
3495
+ // XML lands in the repo working tree rather than the MS PackagesLocalDirectory.
3496
+ resolvedPackageName = actualModelName;
3497
+ basePath =
3498
+ args.packagePath ||
3499
+ customWritePath ||
3500
+ configPackagePath ||
3501
+ fallbackPackagePath();
3502
+ }
3503
+ }
3504
+ console.error(`[create_d365fo_file] Environment: ${envType}, Package: ${resolvedPackageName}, Model: ${actualModelName}, BasePath: ${basePath}`);
3505
+ const modelPath = path.join(basePath, resolvedPackageName, actualModelName, objectFolder);
3506
+ const fileName = `${finalObjectName}.xml`;
3507
+ const fullPath = path.join(modelPath, fileName);
3508
+ // Security: prevent path traversal. path.join() resolves ".." segments,
3509
+ // so a crafted modelName/objectName could escape basePath entirely.
3510
+ // Resolve both paths and assert the target stays within basePath.
3511
+ const resolvedBase = path.resolve(basePath);
3512
+ const resolvedTarget = path.resolve(fullPath);
3513
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
3514
+ throw new Error(`❌ Security error: resolved path "${resolvedTarget}" is outside base directory "${resolvedBase}".\n` +
3515
+ `Check modelName, packageName, objectName, and packagePath for path traversal sequences.`);
3516
+ }
3517
+ // Normalize path to Windows format (backslashes) for consistency
3518
+ const normalizedFullPath = fullPath.replace(/\//g, '\\');
3519
+ // Ensure directory exists (create if needed)
3520
+ const directory = path.dirname(normalizedFullPath);
3521
+ // Verify drive/root exists before attempting recursive mkdir.
3522
+ // path.parse().root works on Windows but returns '' for Windows-style paths on POSIX,
3523
+ // so we extract the drive letter with a regex as a fallback.
3524
+ // (Node.js gives a cryptic '\\?' error when the drive letter doesn't exist)
3525
+ const windowsDriveMatch = /^([A-Za-z]:[/\\])/.exec(normalizedFullPath);
3526
+ const driveOrRoot = windowsDriveMatch ? windowsDriveMatch[1] : path.parse(directory).root; // e.g. "K:\" or "C:\"
3527
+ if (driveOrRoot) {
3528
+ try {
3529
+ await fs.access(driveOrRoot);
3530
+ }
3531
+ catch {
3532
+ const nonWindowsHint = process.platform !== 'win32'
3533
+ ? `\n\n⚠️ This server is running on ${process.platform}. Windows drive letters (${driveOrRoot}) are not accessible.\n` +
3534
+ `Run the MCP server locally on the D365FO Windows VM instead.`
3535
+ : '';
3536
+ throw new Error(`❌ Drive or root path does not exist: ${driveOrRoot}\n\n` +
3537
+ `Attempting to create: ${directory}\n\n` +
3538
+ `The packagePath in your .mcp.json points to a drive that is not accessible.\n` +
3539
+ `Update "packagePath" in .mcp.json to match your actual D365FO installation:\n\n` +
3540
+ `Common paths:\n` +
3541
+ ` C:\\AosService\\PackagesLocalDirectory\n` +
3542
+ ` K:\\AosService\\PackagesLocalDirectory\n` +
3543
+ ` J:\\AosService\\PackagesLocalDirectory\n\n` +
3544
+ `Current packagePath: ${basePath}\n` +
3545
+ `Current drive checked: ${driveOrRoot}${nonWindowsHint}`);
3546
+ }
3547
+ }
3548
+ try {
3549
+ await fs.mkdir(directory, { recursive: true });
3550
+ }
3551
+ catch (mkdirError) {
3552
+ console.error(`[create_d365fo_file] Failed to create directory:`, mkdirError);
3553
+ const hint = (mkdirError instanceof Error && mkdirError.message.includes('\\?'))
3554
+ ? `\n\nHint: The path "${directory}" could not be created. ` +
3555
+ `Verify the drive letter exists and the path is correct. ` +
3556
+ `Update "packagePath" in .mcp.json to fix this.`
3557
+ : '';
3558
+ throw new Error(`Failed to create directory ${directory}: ${mkdirError instanceof Error ? mkdirError.message : 'Unknown error'}${hint}`);
3559
+ }
3560
+ // Check if file already exists
3561
+ let fileExisted = false;
3562
+ try {
3563
+ await fs.access(normalizedFullPath);
3564
+ fileExisted = true;
3565
+ }
3566
+ catch {
3567
+ // File does not exist — normal creation path
3568
+ }
3569
+ if (fileExisted) {
3570
+ if (!args.overwrite) {
3571
+ // Surface what's already on disk so the caller doesn't have to read the
3572
+ // file in chunks just to discover its contents. Previously this branch
3573
+ // returned only "already exists", forcing repeated read_file calls.
3574
+ let existingContent = '';
3575
+ try {
3576
+ existingContent = await fs.readFile(normalizedFullPath, 'utf-8');
3577
+ }
3578
+ catch { /* unreadable — fall through with no summary */ }
3579
+ let existingSummary = '';
3580
+ let inlineContent = '';
3581
+ if (existingContent) {
3582
+ const methodNames = [...existingContent.matchAll(/<Method>\s*<Name>([^<]+)<\/Name>/g)].map(m => m[1]);
3583
+ const fieldNames = [...existingContent.matchAll(/<AxTableField[A-Za-z]*>\s*<Name>([^<]+)<\/Name>/g)].map(m => m[1]);
3584
+ const summaryParts = [];
3585
+ if (methodNames.length) {
3586
+ summaryParts.push(`${methodNames.length} method(s): ${methodNames.slice(0, 30).join(', ')}${methodNames.length > 30 ? ', …' : ''}`);
3587
+ }
3588
+ if (fieldNames.length) {
3589
+ summaryParts.push(`${fieldNames.length} field(s): ${fieldNames.slice(0, 30).join(', ')}${fieldNames.length > 30 ? ', …' : ''}`);
3590
+ }
3591
+ const sizeKb = (Buffer.byteLength(existingContent, 'utf-8') / 1024).toFixed(1);
3592
+ existingSummary = `\n\n📄 Existing file (${sizeKb} KB):` +
3593
+ (summaryParts.length ? `\n ${summaryParts.join('\n ')}` : ' (no methods/fields detected)');
3594
+ // Inline the full content when small enough to be useful in one shot;
3595
+ // otherwise point at the targeted readers rather than dumping a huge file.
3596
+ const INLINE_LIMIT = 8000;
3597
+ inlineContent = existingContent.length <= INLINE_LIMIT
3598
+ ? `\n\n----- BEGIN ${path.basename(normalizedFullPath)} -----\n${existingContent}\n----- END -----`
3599
+ : `\n\n(File is ${sizeKb} KB — too large to inline. Use get_method / get_object_info to read specific members.)`;
3600
+ }
3601
+ // When the requested objectName was normalized to a different on-disk name,
3602
+ // say so explicitly — the file that "already exists" can otherwise look
3603
+ // unrelated to what the caller asked for (e.g. "Foo_Extension" → "FooAc_Extension").
3604
+ const nameNote = finalObjectName !== args.objectName
3605
+ ? `\n\nℹ️ Note: objectName "${args.objectName}" was normalized to "${finalObjectName}" ` +
3606
+ `(active naming style/prefix), so this is the file that matches your request.`
3607
+ : '';
3608
+ return {
3609
+ content: [
3610
+ {
3611
+ type: 'text',
3612
+ text: `⚠️ File already exists: ${normalizedFullPath}${nameNote}${existingSummary}\n\nOptions:\n` +
3613
+ ` 1. Pass overwrite=true together with xmlContent to replace the file.\n` +
3614
+ ` 2. Use d365fo_file(action="modify") to make targeted changes (rename-field, replace-all-fields, modify-property, …).\n` +
3615
+ ` 3. Choose a different objectName.${inlineContent}`,
3616
+ },
3617
+ ],
3618
+ isError: true,
3619
+ };
3620
+ }
3621
+ }
3622
+ // ── Phase 4: Bridge-first creation via IMetadataProvider.Create() ──
3623
+ // For 15 supported types (class, class-extension, table, enum, edt, query, view, form,
3624
+ // menu, 3 menu-items, table/form/enum-extension): try C# bridge first.
3625
+ // Falls back to TypeScript XML generation if bridge unavailable or unsupported type
3626
+ // (report, data-entity, tile, kpi, business-event, security-privilege/duty/role, etc.).
3627
+ //
3628
+ // EXCEPTION — extensible enums: the C# bridge does not set UseEnumValue=No and
3629
+ // emits explicit <Value> elements, both of which xppc rejects with
3630
+ // "UseEnumValue property must be set to 'No' when IsExtensible is True".
3631
+ // Use the TypeScript XML generator (which handles this correctly) instead.
3632
+ //
3633
+ // EXCEPTION — security-privilege/duty/role: excluded from BRIDGE_CREATE_TYPES
3634
+ // entirely (see bridgeAdapter.ts) because the bridge silently drops their
3635
+ // structured collections (EntryPoints, DataEntityPermissions, Privileges, Duties).
3636
+ const skipBridgeForExtensibleEnum = args.objectType === 'enum'
3637
+ && Boolean(args.properties?.isExtensible);
3638
+ if (!args.xmlContent && !skipBridgeForExtensibleEnum && context?.bridge && actualModelName && canBridgeCreate(args.objectType)) {
3639
+ try {
3640
+ // The bridge's `properties` is a flat string map (C# Dictionary<string,string>).
3641
+ // Keep only SCALAR values and stringify them. Structured collections
3642
+ // (fields/fieldGroups/indexes/relations/values/enumValues/methods) are
3643
+ // arrays/objects passed via their own bridge params below — if they leak into
3644
+ // `properties` the C# GetDictParam calls GetString() on a JSON array/boolean and
3645
+ // the whole create throws ("requires an element of type 'String', but the target
3646
+ // element has type 'Array'/'True'").
3647
+ const scalarProperties = args.properties
3648
+ ? Object.fromEntries(Object.entries(args.properties)
3649
+ .filter(([, v]) => v != null && (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean'))
3650
+ .map(([k, v]) => [k, String(v)]))
3651
+ : undefined;
3652
+ // Prepare parameters for the bridge
3653
+ const bridgeParams = {
3654
+ objectType: args.objectType,
3655
+ objectName: finalObjectName,
3656
+ modelName: actualModelName,
3657
+ properties: scalarProperties && Object.keys(scalarProperties).length > 0 ? scalarProperties : undefined,
3658
+ };
3659
+ // For classes: parse sourceCode into declaration + methods
3660
+ if ((args.objectType === 'class' || args.objectType === 'class-extension') && args.sourceCode) {
3661
+ const parsed = XmlTemplateGenerator.parseSourceForBridge(args.sourceCode, finalObjectName);
3662
+ bridgeParams.declaration = parsed.declaration;
3663
+ bridgeParams.methods = parsed.methods;
3664
+ }
3665
+ // For tables AND table-extensions: pass fields, fieldGroups, indexes, relations
3666
+ // from properties. (Previously only 'table' was handled, so a table-extension's
3667
+ // properties.fields were silently dropped and the file got an empty <Fields />.)
3668
+ // Field specs are normalized to the bridge's WriteFieldParam key shape so that
3669
+ // `{ edt, type }` keys are not lost — otherwise every field becomes a bare String.
3670
+ if ((args.objectType === 'table' || args.objectType === 'table-extension') && args.properties) {
3671
+ const props = args.properties;
3672
+ if (props.fields)
3673
+ bridgeParams.fields = normalizeFieldSpecsForBridge(props.fields);
3674
+ if (props.fieldGroups)
3675
+ bridgeParams.fieldGroups = props.fieldGroups;
3676
+ if (props.indexes)
3677
+ bridgeParams.indexes = normalizeIndexSpecsForBridge(props.indexes);
3678
+ if (props.relations)
3679
+ bridgeParams.relations = props.relations;
3680
+ if (props.methods) {
3681
+ bridgeParams.methods = props.methods.map(m => ({
3682
+ ...m,
3683
+ source: m.source !== undefined ? reindentXppSource(m.source) : m.source,
3684
+ }));
3685
+ }
3686
+ // Resolve each field's base type from its EDT when the caller only gave
3687
+ // `edt` (the documented usage — the tool schema says "EDT auto-resolved
3688
+ // when omitted"). Without this, C# CreateTableField() defaults ANY field
3689
+ // whose `type` is unset to AxTableFieldString regardless of the EDT's real
3690
+ // base type — a Real-based EDT (e.g. a rate/amount) or a Date-based EDT
3691
+ // silently becomes a string field. `generateSmartTable`/`generate_object`
3692
+ // already resolve this; the plain d365fo_file(create, table/table-extension)
3693
+ // path never did. Mirrors that resolution: bridge (authoritative) → indexed
3694
+ // edt_metadata chain → name heuristic.
3695
+ if (bridgeParams.fields && bridgeParams.fields.length > 0) {
3696
+ const db = context.symbolIndex?.getReadDb?.();
3697
+ for (const f of bridgeParams.fields) {
3698
+ const edt = f.edt;
3699
+ if (!edt || f.type)
3700
+ continue;
3701
+ if (db && !f.enumType && isEnumName(edt, db)) {
3702
+ f.enumType = edt;
3703
+ f.type = 'Enum';
3704
+ delete f.edt;
3705
+ continue;
3706
+ }
3707
+ const resolved = (await bridgeEdtBaseType(context.bridge, edt))
3708
+ ?? (db ? resolveEdtBaseType(edt, db) : undefined)
3709
+ ?? heuristicEdtBaseType(edt);
3710
+ if (resolved) {
3711
+ f.type = resolved;
3712
+ // An EDT whose OWN base type is Enum (e.g. "Posted" extends "NoYes") only
3713
+ // gets the literal string "Enum" back from the resolvers above — without
3714
+ // the actual enum name, the bridge cannot emit a valid AxTableFieldEnum and
3715
+ // silently falls back to AxTableFieldString. Look up the underlying enum
3716
+ // name so the field is created correctly instead of building "successfully"
3717
+ // as a mistyped string field.
3718
+ if (resolved === 'Enum' && !f.enumType && db) {
3719
+ const enumType = resolveEdtEnumType(edt, db);
3720
+ if (enumType)
3721
+ f.enumType = enumType;
3722
+ }
3723
+ }
3724
+ }
3725
+ }
3726
+ }
3727
+ // For enums AND enum-extensions: pass values from properties.
3728
+ // Accept both `enumValues` (documented in tool description) and `values` (legacy).
3729
+ // Regression: only 'enum' was handled here, so an enum-extension's properties.enumValues
3730
+ // never reached bridgeParams.values — C# CreateEnumExtension() happily accepts a `values`
3731
+ // list, but was always called with null, silently writing an empty <EnumValues />
3732
+ // (write reported success; the dropped value surfaced two calls later as an unrelated
3733
+ // "unresolved enum value" build error). Same class of bug as the table/table-extension
3734
+ // fields gap fixed above.
3735
+ if ((args.objectType === 'enum' || args.objectType === 'enum-extension') && args.properties) {
3736
+ const props = args.properties;
3737
+ const enumVals = (props.enumValues ?? props.values);
3738
+ if (enumVals)
3739
+ bridgeParams.values = enumVals;
3740
+ }
3741
+ // For views: pass fields from properties
3742
+ if (args.objectType === 'view' && args.properties) {
3743
+ const props = args.properties;
3744
+ if (props.fields)
3745
+ bridgeParams.fields = props.fields;
3746
+ }
3747
+ // For EDTs: translate the tool's documented `edtType` property to the bridge's
3748
+ // expected `BaseType` key. C# CreateEdt() does `properties.TryGetValue("BaseType", ...)`
3749
+ // — a literal, case-SENSITIVE dictionary lookup — so sending `edtType` (as documented
3750
+ // in the tool schema and as suggest_edt/prepare recommend) never matched, silently
3751
+ // defaulting every bridge-created EDT to AxEdtString regardless of the requested type
3752
+ // (Real, Int, Date, Enum, ...). Confirmed via a live create of an EDT with
3753
+ // edtType:"Real", extends:"AmountCur" — the written XML came back i:type="AxEdtString".
3754
+ if (args.objectType === 'edt' && bridgeParams.properties && 'edtType' in bridgeParams.properties) {
3755
+ const { edtType, ...rest } = bridgeParams.properties;
3756
+ bridgeParams.properties = { ...rest, BaseType: edtType };
3757
+ }
3758
+ // For plain 'table' creates, prefer the bridge's BP-smart path
3759
+ // (CreateSmartTable) over the generic createObject/CreateTable RPC.
3760
+ // CreateTable writes exactly what the caller passed and nothing more —
3761
+ // no CacheLookup, PrimaryIndex/ClusteredIndex/ReplacementKey, TitleField1/2,
3762
+ // or the 5 standard FieldGroups (AutoReport/AutoLookup/AutoIdentification/
3763
+ // AutoSummary/AutoBrowse) that every real D365FO table gets. generate_object
3764
+ // (mode="scaffold"/"generate", objectType="table") already routes through
3765
+ // CreateSmartTable and gets these correctly; the plain create verb — the
3766
+ // one a generic "create a table" instruction naturally maps to — silently
3767
+ // produced a BP-defaults-free skeleton instead (eval corpus: L1-table-basic,
3768
+ // L3-form-detailstransaction, L4-ssrs-report-basic — golden_diff missing
3769
+ // CacheLookup/ClusteredIndex/PrimaryIndex/ReplacementKey/TitleField1/TitleField2
3770
+ // and all 5 standard FieldGroups). Try the smart path first; any failure or
3771
+ // unavailability falls through to the existing generic bridgeCreateObject/XML
3772
+ // paths below, unchanged.
3773
+ if (args.objectType === 'table') {
3774
+ const rawTableProps = args.properties ?? {};
3775
+ const smartTableGroup = typeof rawTableProps.tableGroup === 'string' ? rawTableProps.tableGroup : undefined;
3776
+ const smartTableType = typeof rawTableProps.tableType === 'string' ? rawTableProps.tableType : undefined;
3777
+ const smartLabel = typeof rawTableProps.label === 'string' ? rawTableProps.label : undefined;
3778
+ const smartExtraProperties = scalarProperties
3779
+ ? Object.fromEntries(Object.entries(scalarProperties).filter(([k]) => !['tableGroup', 'tableType', 'label'].includes(k)))
3780
+ : undefined;
3781
+ try {
3782
+ const smartResult = await bridgeCreateSmartTable(context.bridge, {
3783
+ objectName: finalObjectName,
3784
+ modelName: actualModelName,
3785
+ tableGroup: smartTableGroup,
3786
+ tableType: smartTableType,
3787
+ label: smartLabel ?? finalObjectName,
3788
+ fields: bridgeParams.fields,
3789
+ extraFieldGroups: bridgeParams.fieldGroups,
3790
+ indexes: bridgeParams.indexes,
3791
+ relations: bridgeParams.relations,
3792
+ methods: bridgeParams.methods,
3793
+ extraProperties: smartExtraProperties && Object.keys(smartExtraProperties).length > 0
3794
+ ? smartExtraProperties
3795
+ : undefined,
3796
+ });
3797
+ if (smartResult?.success && smartResult.filePath) {
3798
+ console.error(`[create_d365fo_file] ✅ Created via C# bridge (BP-smart): ${smartResult.filePath}`);
3799
+ let projectMsg = '';
3800
+ if (args.addToProject !== false) {
3801
+ if (projectPathToUse) {
3802
+ try {
3803
+ const projectManager = new ProjectFileManager();
3804
+ await projectManager.addToProject(projectPathToUse, args.objectType, finalObjectName, smartResult.filePath);
3805
+ projectMsg = `\n✅ Added to project: ${path.basename(projectPathToUse)}`;
3806
+ }
3807
+ catch (projErr) {
3808
+ projectMsg = `\n⚠️ Could not add to project: ${projErr}`;
3809
+ }
3810
+ }
3811
+ else if (solutionPathToUse) {
3812
+ try {
3813
+ const detectedPath = await ProjectFileFinder.findProjectInSolution(solutionPathToUse, actualModelName);
3814
+ if (detectedPath) {
3815
+ const projectManager = new ProjectFileManager();
3816
+ await projectManager.addToProject(detectedPath, args.objectType, finalObjectName, smartResult.filePath);
3817
+ projectMsg = `\n✅ Added to project: ${path.basename(detectedPath)}`;
3818
+ }
3819
+ else {
3820
+ projectMsg = `\n⚠️ Could not find .rnrproj for model '${actualModelName}' in ${solutionPathToUse}`;
3821
+ }
3822
+ }
3823
+ catch (projErr) {
3824
+ projectMsg = `\n⚠️ Could not add to project: ${projErr}`;
3825
+ }
3826
+ }
3827
+ else {
3828
+ projectMsg = `\n⚠️ addToProject=true but no projectPath could be resolved.\n` +
3829
+ `Add projectPath to .mcp.json or pass it as a parameter.`;
3830
+ }
3831
+ }
3832
+ // Eagerly refresh the bridge provider so the new object is immediately
3833
+ // resolvable by subsequent modify calls in the same session.
3834
+ try {
3835
+ await bridgeRefreshProvider(context.bridge);
3836
+ }
3837
+ catch { /* best-effort */ }
3838
+ const rawLabelWarning = rawLabelBpWarning(args.properties, finalObjectName);
3839
+ const bp = smartResult.bpDefaults;
3840
+ const bpSummary = bp
3841
+ ? `\n📋 BP defaults: CacheLookup=${bp.cacheLookup ?? '(n/a)'}, TitleField1=${bp.titleField1 ?? '(none)'}, ` +
3842
+ `TitleField2=${bp.titleField2 ?? '(none)'}, PrimaryIndex=${bp.primaryIndex ?? '(none)'}, ` +
3843
+ `ClusteredIndex=${bp.clusteredIndex ?? '(none)'}`
3844
+ : '';
3845
+ return {
3846
+ content: [
3847
+ {
3848
+ type: 'text',
3849
+ text: `✅ Created ${args.objectType} '${finalObjectName}' via IMetadataProvider.Create() (Smart)\n` +
3850
+ `📁 ${smartResult.filePath}${projectMsg}\n` +
3851
+ `🔧 API: ${smartResult.api ?? 'IMetaTableProvider.Create (Smart)'}${bpSummary}${rawLabelWarning}`,
3852
+ },
3853
+ ],
3854
+ };
3855
+ }
3856
+ console.error(`[create_d365fo_file] createSmartTable returned ${JSON.stringify(smartResult)} — falling back to generic bridge create`);
3857
+ }
3858
+ catch (smartErr) {
3859
+ console.error(`[create_d365fo_file] createSmartTable failed, falling back to generic bridge create: ${smartErr}`);
3860
+ }
3861
+ }
3862
+ const bridgeResult = await bridgeCreateObject(context.bridge, bridgeParams);
3863
+ if (bridgeResult?.success && bridgeResult.filePath) {
3864
+ console.error(`[create_d365fo_file] ✅ Created via C# bridge: ${bridgeResult.filePath}`);
3865
+ // Add to .rnrproj if requested
3866
+ let projectMsg = '';
3867
+ if (args.addToProject !== false) {
3868
+ if (projectPathToUse) {
3869
+ try {
3870
+ const projectManager = new ProjectFileManager();
3871
+ await projectManager.addToProject(projectPathToUse, args.objectType, finalObjectName, bridgeResult.filePath);
3872
+ projectMsg = `\n✅ Added to project: ${path.basename(projectPathToUse)}`;
3873
+ }
3874
+ catch (projErr) {
3875
+ projectMsg = `\n⚠️ Could not add to project: ${projErr}`;
3876
+ }
3877
+ }
3878
+ else if (solutionPathToUse) {
3879
+ // Try to find project in solution directory (same logic as XML fallback path)
3880
+ try {
3881
+ const detectedPath = await ProjectFileFinder.findProjectInSolution(solutionPathToUse, actualModelName);
3882
+ if (detectedPath) {
3883
+ const projectManager = new ProjectFileManager();
3884
+ await projectManager.addToProject(detectedPath, args.objectType, finalObjectName, bridgeResult.filePath);
3885
+ projectMsg = `\n✅ Added to project: ${path.basename(detectedPath)}`;
3886
+ }
3887
+ else {
3888
+ projectMsg = `\n⚠️ Could not find .rnrproj for model '${actualModelName}' in ${solutionPathToUse}`;
3889
+ }
3890
+ }
3891
+ catch (projErr) {
3892
+ projectMsg = `\n⚠️ Could not add to project: ${projErr}`;
3893
+ }
3894
+ }
3895
+ else {
3896
+ projectMsg = `\n⚠️ addToProject=true but no projectPath could be resolved.\n` +
3897
+ `Add projectPath to .mcp.json or pass it as a parameter.`;
3898
+ }
3899
+ }
3900
+ // Eagerly refresh the bridge provider so the new object is immediately
3901
+ // resolvable by subsequent modify calls in the same session.
3902
+ try {
3903
+ await bridgeRefreshProvider(context.bridge);
3904
+ }
3905
+ catch { /* best-effort */ }
3906
+ const rawLabelWarning = rawLabelBpWarning(args.properties, finalObjectName);
3907
+ return {
3908
+ content: [
3909
+ {
3910
+ type: 'text',
3911
+ text: `✅ Created ${args.objectType} '${finalObjectName}' via IMetadataProvider.Create()\n` +
3912
+ `📁 ${bridgeResult.filePath}${projectMsg}\n` +
3913
+ `🔧 API: ${bridgeResult.message}${rawLabelWarning}`,
3914
+ },
3915
+ ],
3916
+ };
3917
+ }
3918
+ // If bridge returned null or success=false, fall through to XML generation
3919
+ console.error(`[create_d365fo_file] Bridge returned ${JSON.stringify(bridgeResult)} — falling back to XML generation`);
3920
+ }
3921
+ catch (bridgeErr) {
3922
+ console.error(`[create_d365fo_file] Bridge create failed, falling back to XML: ${bridgeErr}`);
3923
+ }
3924
+ }
3925
+ // Generate (or use provided) XML content
3926
+ let xmlContent = args.xmlContent
3927
+ ? args.xmlContent
3928
+ : XmlTemplateGenerator.generate(args.objectType, finalObjectName, args.sourceCode, args.properties);
3929
+ // Guard against HTML-entity-escaped xmlContent (e.g. "&lt;?xml..." instead of "<?xml...").
3930
+ // This writes silently — no XML parse happens on this path — so a caller mistake here
3931
+ // looks like a success and only breaks on the next build. Found authoring the
3932
+ // L2-numberseq-basic eval case: passing already-escaped XML through xmlContent produced
3933
+ // a file containing literal "&lt;"/"&gt;" instead of real tags.
3934
+ if (args.xmlContent && /&lt;\?xml|&lt;Ax\w/.test(args.xmlContent) && !args.xmlContent.trimStart().startsWith('<')) {
3935
+ throw new Error('xmlContent appears to be HTML-entity-escaped (contains "&lt;" but does not start with a literal "<"). ' +
3936
+ 'Pass raw XML (with literal < and >), not HTML-encoded text — this parameter is written to disk verbatim, unparsed.');
3937
+ }
3938
+ // CRITICAL FIX: Replace unprefixed class/table names with prefixed finalObjectName
3939
+ // When xmlContent or sourceCode contains `class MyClass` but finalObjectName is `MyPrefixMyClass`,
3940
+ // the file would be named MyPrefixMyClass.xml but contain `class MyClass` — inconsistency!
3941
+ if (finalObjectName !== args.objectName && (args.xmlContent || args.sourceCode)) {
3942
+ const orig = args.objectName;
3943
+ const final = finalObjectName;
3944
+ // Escape for use in RegExp (handles dots in extension names like "Foo.Extension")
3945
+ const escapedOrig = orig.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3946
+ // 1. `class OriginalName` / `public class OriginalName` etc.
3947
+ const classPattern = new RegExp(`\\b(public\\s+|private\\s+|protected\\s+|internal\\s+|final\\s+)?class\\s+${escapedOrig}\\b`, 'g');
3948
+ let replaced = xmlContent.replace(classPattern, (match) => match.replace(orig, final));
3949
+ // 2. classnum(OriginalName) — X++ intrinsic that refers to the class by name.
3950
+ // Callers often write classnum(OriginalName) in the source code before prefixing.
3951
+ const classnumPattern = new RegExp(`\\bclassnum\\(\\s*${escapedOrig}\\s*\\)`, 'gi');
3952
+ replaced = replaced.replace(classnumPattern, (m) => m.replace(new RegExp(escapedOrig, 'i'), final));
3953
+ // 3. classStr(OriginalName) — used in [ExtensionOf(classStr(...))] and SysOperation attributes.
3954
+ const classStrPattern = new RegExp(`\\bclassStr\\(\\s*${escapedOrig}\\s*\\)`, 'gi');
3955
+ replaced = replaced.replace(classStrPattern, (m) => m.replace(new RegExp(escapedOrig, 'i'), final));
3956
+ if (replaced !== xmlContent) {
3957
+ console.error(`[create_d365fo_file] ✅ Fixed class name inconsistency: ` +
3958
+ `replaced \`${orig}\` with \`${final}\` in XML content (class decl, classnum, classStr)`);
3959
+ xmlContent = replaced;
3960
+ }
3961
+ }
3962
+ // Sanitize AxReport XML structure — ensures required D365FO VS Designer elements
3963
+ // are always present, regardless of whether xmlContent came from the template or a caller.
3964
+ if (args.objectType === 'report') {
3965
+ xmlContent = XmlTemplateGenerator.sanitizeReportXml(xmlContent);
3966
+ // Convert remaining <Text><![CDATA[…]]></Text> to entity-encoded form.
3967
+ // sanitizeReportXml operates on CDATA internally; this final step converts
3968
+ // the output so that D365FO VS Designer renders the design correctly.
3969
+ xmlContent = XmlTemplateGenerator.encodeReportTextElement(xmlContent);
3970
+ }
3971
+ // Sanitize menu item XML — D365FO metadata deserializer requires
3972
+ // xmlns="Microsoft.Dynamics.AX.Metadata.V1" on the root element.
3973
+ if (args.objectType === 'menu-item-display' ||
3974
+ args.objectType === 'menu-item-action' ||
3975
+ args.objectType === 'menu-item-output') {
3976
+ xmlContent = XmlTemplateGenerator.sanitizeMenuItemXml(xmlContent);
3977
+ }
3978
+ // Sanitize table XML — ensures correct field element format required by D365FO deserializer.
3979
+ if (args.objectType === 'table') {
3980
+ xmlContent = XmlTemplateGenerator.sanitizeTableXml(xmlContent);
3981
+ }
3982
+ // Sanitize query XML — ensures xmlns="" and i:type="AxQuerySimple" on root element.
3983
+ if (args.objectType === 'query') {
3984
+ xmlContent = XmlTemplateGenerator.sanitizeQueryXml(xmlContent);
3985
+ }
3986
+ // Sanitize enum XML — fixes <Values> → <EnumValues> and adds xmlns:i if missing.
3987
+ // Applies to both template-generated and caller-provided xmlContent.
3988
+ if (args.objectType === 'enum') {
3989
+ xmlContent = XmlTemplateGenerator.sanitizeEnumXml(xmlContent);
3990
+ }
3991
+ // Safety net: ensure every pair of adjacent </Method>…<Method> is separated by
3992
+ // exactly one blank line. This guards against xmlContent supplied by callers
3993
+ // (e.g. from generate or generate_d365fo_xml) that might already be
3994
+ // correct, or against edge-cases in the generator that produces no blank line.
3995
+ // The replacement is idempotent: \n\n\n → \n\n (no double-blank lines created).
3996
+ xmlContent = xmlContent.replace(/<\/Method>\n(\t*)<Method>/g, '</Method>\n\n$1<Method>');
3997
+ // Form pattern gate: structural pattern violations (FP001-FP005, FP007)
3998
+ // block the write when FORM_PATTERN_ENFORCE is enabled (default).
3999
+ // Recommendations are appended to the success message instead.
4000
+ let formPatternWarnings = '';
4001
+ if (args.objectType === 'form') {
4002
+ const gate = await gateOnFormPatternErrors(xmlContent, `d365fo_file(action="create", form ${finalObjectName})`);
4003
+ if (gate.blocked) {
4004
+ return gate.blocked;
4005
+ }
4006
+ if (gate.warningsText) {
4007
+ formPatternWarnings = `\n${gate.warningsText}\n`;
4008
+ }
4009
+ }
4010
+ // Form-extension control-shape gate: reject the malformed control shapes an AI
4011
+ // tends to hand-write (<AxFormControlExtension>, <ParentControlName>,
4012
+ // <FormControlExtension> wrapping the control, AxFormIntControl) — they pass XML
4013
+ // well-formedness but the D365FO deserializer rejects them. Blocks when
4014
+ // FORM_PATTERN_ENFORCE is on (default), else appends a warning.
4015
+ if (args.objectType === 'form-extension' && args.xmlContent) {
4016
+ const shapeProblems = validateFormExtensionControlShape(xmlContent);
4017
+ if (shapeProblems.length > 0) {
4018
+ const shapeError = buildFormExtensionShapeError(finalObjectName, shapeProblems);
4019
+ if (isFormPatternEnforceEnabled()) {
4020
+ return { content: [{ type: 'text', text: shapeError }], isError: true };
4021
+ }
4022
+ formPatternWarnings += `\n⚠️ ${shapeError}\n`;
4023
+ }
4024
+ }
4025
+ // Debug: Log XML content length
4026
+ const xmlSource = args.xmlContent ? 'provided by caller' : 'generated from template';
4027
+ console.error(`[create_d365fo_file] XML content (${xmlSource}): ${xmlContent.length} bytes`);
4028
+ console.error(`[create_d365fo_file] XML preview: ${xmlContent.substring(0, 200)}...`);
4029
+ // Write file matching D365FO convention: no BOM, CRLF, no trailing newline
4030
+ try {
4031
+ await fs.writeFile(normalizedFullPath, normalizeD365Xml(xmlContent), 'utf-8');
4032
+ }
4033
+ catch (writeError) {
4034
+ console.error(`[create_d365fo_file] Failed to write file:`, writeError);
4035
+ // Check if it's a disk/path issue
4036
+ const errorMessage = writeError instanceof Error ? writeError.message : String(writeError);
4037
+ if (errorMessage.includes('EINVAL') || errorMessage.includes('ENOENT')) {
4038
+ throw new Error(`Failed to write file to ${normalizedFullPath}.\n\n` +
4039
+ `Possible causes:\n` +
4040
+ `1. Drive K:\\ does not exist (running on Linux/Mac? Use packagePath parameter to override)\n` +
4041
+ `2. Directory ${path.dirname(normalizedFullPath)} is not accessible\n` +
4042
+ `3. Insufficient permissions\n\n` +
4043
+ `Original error: ${errorMessage}`);
4044
+ }
4045
+ throw writeError;
4046
+ }
4047
+ // Verify file was written
4048
+ const stats = await fs.stat(normalizedFullPath);
4049
+ const fileSizeKb = (stats.size / 1024).toFixed(1);
4050
+ console.error(`[create_d365fo_file] ✅ Written: ${normalizedFullPath} (${fileSizeKb} KB)`);
4051
+ // Eagerly refresh the bridge provider so the new object is immediately
4052
+ // resolvable by subsequent modify calls in the same session.
4053
+ try {
4054
+ await bridgeRefreshProvider(context?.bridge);
4055
+ }
4056
+ catch { /* best-effort */ }
4057
+ // Post-write validation via C# bridge (best-effort, non-fatal, fire-and-forget).
4058
+ // Not awaited: the validation goes through the sequential bridge stdin/stdout
4059
+ // pipe and can take 60s+, which would block all subsequent MCP calls.
4060
+ // See: https://github.com/dynamics365ninja/d365fo-mcp-server/issues/407
4061
+ let bridgeValidation = '';
4062
+ bridgeValidateAfterWrite(context?.bridge, args.objectType, finalObjectName).then(validationMsg => {
4063
+ if (validationMsg) {
4064
+ console.error(`[create_d365fo_file] Bridge validation: ${validationMsg}`);
4065
+ }
4066
+ }).catch(e => {
4067
+ console.error(`[create_d365fo_file] Bridge validation skipped: ${e}`);
4068
+ });
4069
+ // Add to Visual Studio project if requested
4070
+ let projectMessage = '';
4071
+ if (args.addToProject) {
4072
+ // Try to find project file if not explicitly specified
4073
+ // Use projectPathToUse which includes values from .mcp.json config
4074
+ let projectPath = projectPathToUse;
4075
+ if (!projectPath && solutionPathToUse) {
4076
+ // Try to find project in solution directory
4077
+ // Use solutionPathToUse which includes values from .mcp.json config
4078
+ console.error(`[create_d365fo_file] Searching for .rnrproj in solution: ${solutionPathToUse}, model: ${actualModelName}`);
4079
+ const detectedPath = await ProjectFileFinder.findProjectInSolution(solutionPathToUse, actualModelName);
4080
+ if (!detectedPath) {
4081
+ console.error(`[create_d365fo_file] No .rnrproj found in solution directory`);
4082
+ projectMessage = `\n⚠️ Could not find .rnrproj file for model '${actualModelName}' in solution directory.\n` +
4083
+ `Searched in: ${solutionPathToUse}\n` +
4084
+ `Please specify projectPath parameter explicitly or add it to .mcp.json.\n`;
4085
+ }
4086
+ else {
4087
+ console.error(`[create_d365fo_file] Found project file: ${detectedPath}`);
4088
+ projectPath = detectedPath;
4089
+ }
4090
+ }
4091
+ else if (!projectPath) {
4092
+ projectMessage = `\n⚠️ Cannot add to project: projectPath could not be resolved.\n` +
4093
+ `Add projectPath to .mcp.json config, or pass it as a tool argument.\n` +
4094
+ `Example .mcp.json: { "servers": { "context": { "projectPath": "K:\\\\VSProjects\\\\MySolution\\\\MyModel\\\\MyModel.rnrproj" } } }\n`;
4095
+ }
4096
+ if (projectPath) {
4097
+ try {
4098
+ // Validate project file exists
4099
+ await fs.access(projectPath);
4100
+ // D365FO projects expect ABSOLUTE paths to XML files, not relative
4101
+ // The full path must point to the exact XML location in PackagesLocalDirectory
4102
+ // Ensure Windows path format with backslashes
4103
+ const absoluteXmlPath = normalizedFullPath;
4104
+ // Add to project
4105
+ const projectManager = new ProjectFileManager();
4106
+ const wasAdded = await projectManager.addToProject(projectPath, args.objectType, finalObjectName, absoluteXmlPath);
4107
+ if (wasAdded) {
4108
+ console.error(`[create_d365fo_file] Successfully added to project`);
4109
+ projectMessage = `\n✅ Successfully added to Visual Studio project:\n📋 Project: ${projectPath}\n` +
4110
+ `ℹ️ If the file does not appear in VS Solution Explorer, right-click the project → Reload Project.`;
4111
+ }
4112
+ else {
4113
+ console.error(`[create_d365fo_file] File already exists in project`);
4114
+ projectMessage = `\n✅ File already exists in Visual Studio project:\n📋 Project: ${projectPath}\n`;
4115
+ }
4116
+ }
4117
+ catch (projectError) {
4118
+ const errMsg = projectError instanceof Error ? projectError.message : 'Unknown error';
4119
+ const isLocked = errMsg.includes('EBUSY') || errMsg.includes('EPERM') || errMsg.includes('EACCES');
4120
+ console.error(`[create_d365fo_file] Failed to add to project:`, projectError);
4121
+ projectMessage = `\n⚠️ File created but failed to add to project:\n${errMsg}\n` +
4122
+ (isLocked
4123
+ ? `This usually means Visual Studio has the .rnrproj file locked.\n` +
4124
+ `Close Visual Studio (or unload the project), re-run the tool, then reopen.\n`
4125
+ : '');
4126
+ }
4127
+ }
4128
+ else if (!projectMessage) {
4129
+ // No projectPath found from any source — surface this in the response so AI and user see it
4130
+ projectMessage = `\n⚠️ addToProject=true but no projectPath could be resolved.\n` +
4131
+ `The file was created on disk but was NOT added to the Visual Studio project.\n\n` +
4132
+ `To fix this, add projectPath to your .mcp.json:\n` +
4133
+ ` {\n` +
4134
+ ` "servers": { "context": {\n` +
4135
+ ` "projectPath": "K:\\\\VSProjects\\\\YourSolution\\\\YourModel\\\\YourModel.rnrproj"\n` +
4136
+ ` } }\n` +
4137
+ ` }\n` +
4138
+ `Until then, add the file manually in Visual Studio: right-click project → Add Existing Item → ${normalizedFullPath}\n`;
4139
+ }
4140
+ }
4141
+ // Build success message
4142
+ const nextSteps = args.addToProject
4143
+ ? `Next steps:\n` +
4144
+ `1. Reload project in Visual Studio (or close/reopen solution)\n` +
4145
+ `2. Build the project to synchronize the object\n` +
4146
+ `3. Refresh AOT in Visual Studio to see the new object\n`
4147
+ : `Next steps:\n` +
4148
+ `1. Add the file to your Visual Studio project (.rnrproj)\n` +
4149
+ `2. Build the project to synchronize the object\n` +
4150
+ `3. Refresh AOT in Visual Studio to see the new object\n`;
4151
+ // Return success message with file path
4152
+ return {
4153
+ content: [
4154
+ {
4155
+ type: 'text',
4156
+ text: `✅ Successfully created D365FO ${args.objectType} file:\n\n` +
4157
+ `📁 Path: ${normalizedFullPath}\n` +
4158
+ `📄 Object: ${finalObjectName}${finalObjectName !== args.objectName ? ` (prefixed from "${args.objectName}")` : ''}\n` +
4159
+ `📦 Model: ${actualModelName}\n` +
4160
+ `🔧 Type: ${objectFolder}\n` +
4161
+ bridgeValidation +
4162
+ formPatternWarnings +
4163
+ rawLabelBpWarning(args.properties, finalObjectName) +
4164
+ projectMessage +
4165
+ `\n${nextSteps}\n` +
4166
+ `⛔ TASK COMPLETE — do NOT call \`generate\`, \`generate\`, or \`d365fo_file(action="create")\` again for this object.`,
4167
+ },
4168
+ ],
4169
+ };
4170
+ }
4171
+ catch (error) {
4172
+ return {
4173
+ content: [
4174
+ {
4175
+ type: 'text',
4176
+ text: `❌ Error creating D365FO file:\n\n${error instanceof Error ? error.message : 'Unknown error'}`,
4177
+ },
4178
+ ],
4179
+ isError: true,
4180
+ };
4181
+ }
4182
+ }
4183
+ // Tool registration (name, description, inputSchema) lives inline in
4184
+ // src/server/mcpServer.ts - the single source of truth for tool instructions.
4185
+ //# sourceMappingURL=createD365File.js.map