@servicenow/sdk-build-plugins 4.2.0 → 4.4.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 (349) hide show
  1. package/dist/acl-plugin.js +11 -0
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.d.ts +2 -0
  4. package/dist/applicability-plugin.js +72 -0
  5. package/dist/applicability-plugin.js.map +1 -0
  6. package/dist/atf/test-plugin.js +5 -2
  7. package/dist/atf/test-plugin.js.map +1 -1
  8. package/dist/basic-syntax-plugin.js +7 -1
  9. package/dist/basic-syntax-plugin.js.map +1 -1
  10. package/dist/business-rule-plugin.js +1 -0
  11. package/dist/business-rule-plugin.js.map +1 -1
  12. package/dist/call-expression-plugin.js +1 -107
  13. package/dist/call-expression-plugin.js.map +1 -1
  14. package/dist/column/column-to-record.d.ts +10 -3
  15. package/dist/column/column-to-record.js +44 -7
  16. package/dist/column/column-to-record.js.map +1 -1
  17. package/dist/column-plugin.d.ts +3 -1
  18. package/dist/column-plugin.js +12 -12
  19. package/dist/column-plugin.js.map +1 -1
  20. package/dist/dashboard/dashboard-component-property-defaults.d.ts +152 -0
  21. package/dist/dashboard/dashboard-component-property-defaults.js +264 -0
  22. package/dist/dashboard/dashboard-component-property-defaults.js.map +1 -0
  23. package/dist/dashboard/dashboard-component-resolver.d.ts +13 -0
  24. package/dist/dashboard/dashboard-component-resolver.js +69 -0
  25. package/dist/dashboard/dashboard-component-resolver.js.map +1 -0
  26. package/dist/dashboard/dashboard-plugin.d.ts +12 -0
  27. package/dist/dashboard/dashboard-plugin.js +397 -0
  28. package/dist/dashboard/dashboard-plugin.js.map +1 -0
  29. package/dist/data-plugin.d.ts +3 -0
  30. package/dist/data-plugin.js +61 -113
  31. package/dist/data-plugin.js.map +1 -1
  32. package/dist/email-notification-plugin.d.ts +2 -0
  33. package/dist/email-notification-plugin.js +541 -0
  34. package/dist/email-notification-plugin.js.map +1 -0
  35. package/dist/flow/constants/flow-plugin-constants.d.ts +58 -0
  36. package/dist/flow/constants/flow-plugin-constants.js +70 -0
  37. package/dist/flow/constants/flow-plugin-constants.js.map +1 -0
  38. package/dist/flow/flow-logic/flow-logic-constants.d.ts +38 -0
  39. package/dist/flow/flow-logic/flow-logic-constants.js +118 -0
  40. package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -0
  41. package/dist/flow/flow-logic/flow-logic-diagnostics.d.ts +19 -0
  42. package/dist/flow/flow-logic/flow-logic-diagnostics.js +503 -0
  43. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -0
  44. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +62 -0
  45. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +2092 -0
  46. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -0
  47. package/dist/flow/flow-logic/flow-logic-plugin.d.ts +52 -0
  48. package/dist/flow/flow-logic/flow-logic-plugin.js +283 -0
  49. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -0
  50. package/dist/flow/flow-logic/flow-logic-shapes.d.ts +104 -0
  51. package/dist/flow/flow-logic/flow-logic-shapes.js +201 -0
  52. package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -0
  53. package/dist/flow/plugins/approval-rules-plugin.d.ts +2 -0
  54. package/dist/flow/plugins/approval-rules-plugin.js +49 -0
  55. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -0
  56. package/dist/flow/plugins/flow-action-definition-plugin.d.ts +2 -0
  57. package/dist/flow/plugins/flow-action-definition-plugin.js +286 -0
  58. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -0
  59. package/dist/flow/plugins/flow-data-pill-plugin.d.ts +9 -0
  60. package/dist/flow/plugins/flow-data-pill-plugin.js +212 -0
  61. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -0
  62. package/dist/flow/plugins/flow-definition-plugin.d.ts +2 -0
  63. package/dist/flow/plugins/flow-definition-plugin.js +1668 -0
  64. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -0
  65. package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +26 -0
  66. package/dist/flow/plugins/flow-diagnostics-plugin.js +217 -0
  67. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -0
  68. package/dist/flow/plugins/flow-instance-plugin.d.ts +12 -0
  69. package/dist/flow/plugins/flow-instance-plugin.js +1205 -0
  70. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -0
  71. package/dist/flow/plugins/flow-trigger-instance-plugin.d.ts +2 -0
  72. package/dist/flow/plugins/flow-trigger-instance-plugin.js +338 -0
  73. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -0
  74. package/dist/flow/plugins/inline-script-plugin.d.ts +39 -0
  75. package/dist/flow/plugins/inline-script-plugin.js +80 -0
  76. package/dist/flow/plugins/inline-script-plugin.js.map +1 -0
  77. package/dist/flow/plugins/step-definition-plugin.d.ts +5 -0
  78. package/dist/flow/plugins/step-definition-plugin.js +71 -0
  79. package/dist/flow/plugins/step-definition-plugin.js.map +1 -0
  80. package/dist/flow/plugins/step-instance-plugin.d.ts +31 -0
  81. package/dist/flow/plugins/step-instance-plugin.js +339 -0
  82. package/dist/flow/plugins/step-instance-plugin.js.map +1 -0
  83. package/dist/flow/plugins/trigger-plugin.d.ts +2 -0
  84. package/dist/flow/plugins/trigger-plugin.js +96 -0
  85. package/dist/flow/plugins/trigger-plugin.js.map +1 -0
  86. package/dist/flow/plugins/wfa-datapill-plugin.d.ts +15 -0
  87. package/dist/flow/plugins/wfa-datapill-plugin.js +178 -0
  88. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -0
  89. package/dist/flow/utils/approval-rules-processor.d.ts +13 -0
  90. package/dist/flow/utils/approval-rules-processor.js +267 -0
  91. package/dist/flow/utils/approval-rules-processor.js.map +1 -0
  92. package/dist/flow/utils/built-in-complex-objects.d.ts +19 -0
  93. package/dist/flow/utils/built-in-complex-objects.js +62 -0
  94. package/dist/flow/utils/built-in-complex-objects.js.map +1 -0
  95. package/dist/flow/utils/complex-object-resolver.d.ts +8 -0
  96. package/dist/flow/utils/complex-object-resolver.js +614 -0
  97. package/dist/flow/utils/complex-object-resolver.js.map +1 -0
  98. package/dist/flow/utils/complex-objects.d.ts +36 -0
  99. package/dist/flow/utils/complex-objects.js +481 -0
  100. package/dist/flow/utils/complex-objects.js.map +1 -0
  101. package/dist/flow/utils/data-pill-shapes.d.ts +58 -0
  102. package/dist/flow/utils/data-pill-shapes.js +135 -0
  103. package/dist/flow/utils/data-pill-shapes.js.map +1 -0
  104. package/dist/flow/utils/datapill-transformer.d.ts +110 -0
  105. package/dist/flow/utils/datapill-transformer.js +503 -0
  106. package/dist/flow/utils/datapill-transformer.js.map +1 -0
  107. package/dist/flow/utils/flow-constants.d.ts +72 -0
  108. package/dist/flow/utils/flow-constants.js +230 -0
  109. package/dist/flow/utils/flow-constants.js.map +1 -0
  110. package/dist/flow/utils/flow-io-to-record.d.ts +44 -0
  111. package/dist/flow/utils/flow-io-to-record.js +409 -0
  112. package/dist/flow/utils/flow-io-to-record.js.map +1 -0
  113. package/dist/flow/utils/flow-shapes.d.ts +161 -0
  114. package/dist/flow/utils/flow-shapes.js +255 -0
  115. package/dist/flow/utils/flow-shapes.js.map +1 -0
  116. package/dist/flow/utils/flow-to-xml.d.ts +16 -0
  117. package/dist/flow/utils/flow-to-xml.js +237 -0
  118. package/dist/flow/utils/flow-to-xml.js.map +1 -0
  119. package/dist/flow/utils/flow-variable-processor.d.ts +51 -0
  120. package/dist/flow/utils/flow-variable-processor.js +69 -0
  121. package/dist/flow/utils/flow-variable-processor.js.map +1 -0
  122. package/dist/flow/utils/label-cache-parser.d.ts +7 -0
  123. package/dist/flow/utils/label-cache-parser.js +24 -0
  124. package/dist/flow/utils/label-cache-parser.js.map +1 -0
  125. package/dist/flow/utils/label-cache-processor.d.ts +119 -0
  126. package/dist/flow/utils/label-cache-processor.js +719 -0
  127. package/dist/flow/utils/label-cache-processor.js.map +1 -0
  128. package/dist/flow/utils/pill-string-parser.d.ts +88 -0
  129. package/dist/flow/utils/pill-string-parser.js +306 -0
  130. package/dist/flow/utils/pill-string-parser.js.map +1 -0
  131. package/dist/flow/utils/schema-to-flow-object.d.ts +22 -0
  132. package/dist/flow/utils/schema-to-flow-object.js +318 -0
  133. package/dist/flow/utils/schema-to-flow-object.js.map +1 -0
  134. package/dist/flow/utils/service-catalog.d.ts +47 -0
  135. package/dist/flow/utils/service-catalog.js +137 -0
  136. package/dist/flow/utils/service-catalog.js.map +1 -0
  137. package/dist/flow/utils/utils.d.ts +117 -0
  138. package/dist/flow/utils/utils.js +345 -0
  139. package/dist/flow/utils/utils.js.map +1 -0
  140. package/dist/index.d.ts +20 -1
  141. package/dist/index.js +21 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/list-plugin.js +1 -1
  144. package/dist/list-plugin.js.map +1 -1
  145. package/dist/now-attach-plugin.d.ts +1 -0
  146. package/dist/now-attach-plugin.js +10 -10
  147. package/dist/now-attach-plugin.js.map +1 -1
  148. package/dist/now-ref-plugin.js +1 -1
  149. package/dist/now-ref-plugin.js.map +1 -1
  150. package/dist/record-plugin.d.ts +29 -0
  151. package/dist/record-plugin.js +66 -7
  152. package/dist/record-plugin.js.map +1 -1
  153. package/dist/repack/index.d.ts +2 -0
  154. package/dist/repack/index.js +8 -0
  155. package/dist/repack/index.js.map +1 -1
  156. package/dist/rest-api-plugin.js +54 -44
  157. package/dist/rest-api-plugin.js.map +1 -1
  158. package/dist/server-module-plugin/index.d.ts +10 -0
  159. package/dist/server-module-plugin/index.js +83 -59
  160. package/dist/server-module-plugin/index.js.map +1 -1
  161. package/dist/service-catalog/catalog-clientscript-plugin.d.ts +2 -0
  162. package/dist/service-catalog/catalog-clientscript-plugin.js +117 -0
  163. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -0
  164. package/dist/service-catalog/catalog-item-plugin.d.ts +2 -0
  165. package/dist/service-catalog/catalog-item-plugin.js +115 -0
  166. package/dist/service-catalog/catalog-item-plugin.js.map +1 -0
  167. package/dist/service-catalog/catalog-ui-policy-plugin.d.ts +2 -0
  168. package/dist/service-catalog/catalog-ui-policy-plugin.js +266 -0
  169. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -0
  170. package/dist/service-catalog/index.d.ts +5 -0
  171. package/dist/service-catalog/index.js +22 -0
  172. package/dist/service-catalog/index.js.map +1 -0
  173. package/dist/service-catalog/record-to-shape.d.ts +6 -0
  174. package/dist/service-catalog/record-to-shape.js +93 -0
  175. package/dist/service-catalog/record-to-shape.js.map +1 -0
  176. package/dist/service-catalog/sc-record-producer-plugin.d.ts +2 -0
  177. package/dist/service-catalog/sc-record-producer-plugin.js +140 -0
  178. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -0
  179. package/dist/service-catalog/service-catalog-base.d.ts +311 -0
  180. package/dist/service-catalog/service-catalog-base.js +542 -0
  181. package/dist/service-catalog/service-catalog-base.js.map +1 -0
  182. package/dist/service-catalog/service-catalog-diagnostics.d.ts +45 -0
  183. package/dist/service-catalog/service-catalog-diagnostics.js +172 -0
  184. package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -0
  185. package/dist/service-catalog/shape-to-record.d.ts +8 -0
  186. package/dist/service-catalog/shape-to-record.js +235 -0
  187. package/dist/service-catalog/shape-to-record.js.map +1 -0
  188. package/dist/service-catalog/utils.d.ts +323 -0
  189. package/dist/service-catalog/utils.js +1216 -0
  190. package/dist/service-catalog/utils.js.map +1 -0
  191. package/dist/service-catalog/variable-helper.d.ts +43 -0
  192. package/dist/service-catalog/variable-helper.js +92 -0
  193. package/dist/service-catalog/variable-helper.js.map +1 -0
  194. package/dist/service-catalog/variable-set-plugin.d.ts +2 -0
  195. package/dist/service-catalog/variable-set-plugin.js +175 -0
  196. package/dist/service-catalog/variable-set-plugin.js.map +1 -0
  197. package/dist/service-catalog/variables-transform.d.ts +139 -0
  198. package/dist/service-catalog/variables-transform.js +403 -0
  199. package/dist/service-catalog/variables-transform.js.map +1 -0
  200. package/dist/sla/sla-validators.d.ts +61 -0
  201. package/dist/sla/sla-validators.js +224 -0
  202. package/dist/sla/sla-validators.js.map +1 -0
  203. package/dist/sla-plugin.d.ts +5 -0
  204. package/dist/sla-plugin.js +280 -0
  205. package/dist/sla-plugin.js.map +1 -0
  206. package/dist/static-content-plugin.js +25 -2
  207. package/dist/static-content-plugin.js.map +1 -1
  208. package/dist/table-plugin.js +32 -15
  209. package/dist/table-plugin.js.map +1 -1
  210. package/dist/ui-page-plugin.js +832 -19
  211. package/dist/ui-page-plugin.js.map +1 -1
  212. package/dist/ui-policy-plugin.js +5 -7
  213. package/dist/ui-policy-plugin.js.map +1 -1
  214. package/dist/utils.d.ts +10 -1
  215. package/dist/utils.js +16 -0
  216. package/dist/utils.js.map +1 -1
  217. package/dist/ux-list-menu-config-plugin.d.ts +2 -0
  218. package/dist/ux-list-menu-config-plugin.js +292 -0
  219. package/dist/ux-list-menu-config-plugin.js.map +1 -0
  220. package/dist/workspace-plugin/chrome-tab.d.ts +2 -0
  221. package/dist/workspace-plugin/chrome-tab.js +46 -0
  222. package/dist/workspace-plugin/chrome-tab.js.map +1 -0
  223. package/dist/workspace-plugin/constants.d.ts +52 -0
  224. package/dist/workspace-plugin/constants.js +56 -0
  225. package/dist/workspace-plugin/constants.js.map +1 -0
  226. package/dist/workspace-plugin/fluent-utils.d.ts +9 -0
  227. package/dist/workspace-plugin/fluent-utils.js +60 -0
  228. package/dist/workspace-plugin/fluent-utils.js.map +1 -0
  229. package/dist/workspace-plugin/page.d.ts +8 -0
  230. package/dist/workspace-plugin/page.js +108 -0
  231. package/dist/workspace-plugin/page.js.map +1 -0
  232. package/dist/workspace-plugin/screen.d.ts +1 -0
  233. package/dist/workspace-plugin/screen.js +38 -0
  234. package/dist/workspace-plugin/screen.js.map +1 -0
  235. package/dist/workspace-plugin/templates/index.d.ts +10 -0
  236. package/dist/workspace-plugin/templates/index.js +20 -0
  237. package/dist/workspace-plugin/templates/index.js.map +1 -0
  238. package/dist/workspace-plugin/templates/record-page-composition.d.ts +1 -0
  239. package/dist/workspace-plugin/templates/record-page-composition.js +4043 -0
  240. package/dist/workspace-plugin/templates/record-page-composition.js.map +1 -0
  241. package/dist/workspace-plugin/templates/record-page-data.d.ts +1 -0
  242. package/dist/workspace-plugin/templates/record-page-data.js +527 -0
  243. package/dist/workspace-plugin/templates/record-page-data.js.map +1 -0
  244. package/dist/workspace-plugin/templates/record-page-interalEventMappings.d.ts +1 -0
  245. package/dist/workspace-plugin/templates/record-page-interalEventMappings.js +39 -0
  246. package/dist/workspace-plugin/templates/record-page-interalEventMappings.js.map +1 -0
  247. package/dist/workspace-plugin/templates/record-page-layoutModel.d.ts +1 -0
  248. package/dist/workspace-plugin/templates/record-page-layoutModel.js +55 -0
  249. package/dist/workspace-plugin/templates/record-page-layoutModel.js.map +1 -0
  250. package/dist/workspace-plugin/templates/record-page-properties.d.ts +1 -0
  251. package/dist/workspace-plugin/templates/record-page-properties.js +135 -0
  252. package/dist/workspace-plugin/templates/record-page-properties.js.map +1 -0
  253. package/dist/workspace-plugin/templates/record-page.d.ts +3 -0
  254. package/dist/workspace-plugin/templates/record-page.js +8 -0
  255. package/dist/workspace-plugin/templates/record-page.js.map +1 -0
  256. package/dist/workspace-plugin.d.ts +2 -0
  257. package/dist/workspace-plugin.js +453 -0
  258. package/dist/workspace-plugin.js.map +1 -0
  259. package/package.json +10 -12
  260. package/src/acl-plugin.ts +16 -1
  261. package/src/applicability-plugin.ts +82 -0
  262. package/src/atf/test-plugin.ts +6 -3
  263. package/src/basic-syntax-plugin.ts +10 -1
  264. package/src/business-rule-plugin.ts +2 -1
  265. package/src/call-expression-plugin.ts +2 -130
  266. package/src/column/column-to-record.ts +54 -8
  267. package/src/column-plugin.ts +29 -13
  268. package/src/dashboard/dashboard-component-property-defaults.ts +277 -0
  269. package/src/dashboard/dashboard-component-resolver.ts +69 -0
  270. package/src/dashboard/dashboard-plugin.ts +450 -0
  271. package/src/data-plugin.ts +67 -139
  272. package/src/email-notification-plugin.ts +850 -0
  273. package/src/flow/constants/flow-plugin-constants.ts +79 -0
  274. package/src/flow/flow-logic/flow-logic-constants.ts +120 -0
  275. package/src/flow/flow-logic/flow-logic-diagnostics.ts +591 -0
  276. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +2550 -0
  277. package/src/flow/flow-logic/flow-logic-plugin.ts +337 -0
  278. package/src/flow/flow-logic/flow-logic-shapes.ts +215 -0
  279. package/src/flow/plugins/approval-rules-plugin.ts +48 -0
  280. package/src/flow/plugins/flow-action-definition-plugin.ts +295 -0
  281. package/src/flow/plugins/flow-data-pill-plugin.ts +258 -0
  282. package/src/flow/plugins/flow-definition-plugin.ts +2173 -0
  283. package/src/flow/plugins/flow-diagnostics-plugin.ts +280 -0
  284. package/src/flow/plugins/flow-instance-plugin.ts +1499 -0
  285. package/src/flow/plugins/flow-trigger-instance-plugin.ts +444 -0
  286. package/src/flow/plugins/inline-script-plugin.ts +83 -0
  287. package/src/flow/plugins/step-definition-plugin.ts +67 -0
  288. package/src/flow/plugins/step-instance-plugin.ts +431 -0
  289. package/src/flow/plugins/trigger-plugin.ts +95 -0
  290. package/src/flow/plugins/wfa-datapill-plugin.ts +213 -0
  291. package/src/flow/utils/approval-rules-processor.ts +298 -0
  292. package/src/flow/utils/built-in-complex-objects.ts +81 -0
  293. package/src/flow/utils/complex-object-resolver.ts +875 -0
  294. package/src/flow/utils/complex-objects.ts +656 -0
  295. package/src/flow/utils/data-pill-shapes.ts +165 -0
  296. package/src/flow/utils/datapill-transformer.ts +632 -0
  297. package/src/flow/utils/flow-constants.ts +285 -0
  298. package/src/flow/utils/flow-io-to-record.ts +533 -0
  299. package/src/flow/utils/flow-shapes.ts +296 -0
  300. package/src/flow/utils/flow-to-xml.ts +318 -0
  301. package/src/flow/utils/flow-variable-processor.ts +100 -0
  302. package/src/flow/utils/label-cache-parser.ts +37 -0
  303. package/src/flow/utils/label-cache-processor.ts +870 -0
  304. package/src/flow/utils/pill-string-parser.ts +375 -0
  305. package/src/flow/utils/schema-to-flow-object.ts +385 -0
  306. package/src/flow/utils/service-catalog.ts +174 -0
  307. package/src/flow/utils/utils.ts +395 -0
  308. package/src/index.ts +20 -1
  309. package/src/list-plugin.ts +1 -1
  310. package/src/now-attach-plugin.ts +14 -11
  311. package/src/now-ref-plugin.ts +1 -1
  312. package/src/record-plugin.ts +76 -11
  313. package/src/repack/index.ts +14 -0
  314. package/src/rest-api-plugin.ts +62 -50
  315. package/src/server-module-plugin/index.ts +112 -86
  316. package/src/service-catalog/catalog-clientscript-plugin.ts +140 -0
  317. package/src/service-catalog/catalog-item-plugin.ts +162 -0
  318. package/src/service-catalog/catalog-ui-policy-plugin.ts +324 -0
  319. package/src/service-catalog/index.ts +5 -0
  320. package/src/service-catalog/record-to-shape.ts +109 -0
  321. package/src/service-catalog/sc-record-producer-plugin.ts +201 -0
  322. package/src/service-catalog/service-catalog-base.ts +600 -0
  323. package/src/service-catalog/service-catalog-diagnostics.ts +254 -0
  324. package/src/service-catalog/shape-to-record.ts +279 -0
  325. package/src/service-catalog/utils.ts +1455 -0
  326. package/src/service-catalog/variable-helper.ts +135 -0
  327. package/src/service-catalog/variable-set-plugin.ts +197 -0
  328. package/src/service-catalog/variables-transform.ts +438 -0
  329. package/src/sla/sla-validators.ts +331 -0
  330. package/src/sla-plugin.ts +358 -0
  331. package/src/static-content-plugin.ts +25 -2
  332. package/src/table-plugin.ts +49 -16
  333. package/src/ui-page-plugin.ts +1063 -20
  334. package/src/ui-policy-plugin.ts +5 -9
  335. package/src/utils.ts +24 -1
  336. package/src/ux-list-menu-config-plugin.ts +312 -0
  337. package/src/workspace-plugin/chrome-tab.ts +44 -0
  338. package/src/workspace-plugin/constants.ts +53 -0
  339. package/src/workspace-plugin/fluent-utils.ts +60 -0
  340. package/src/workspace-plugin/page.ts +139 -0
  341. package/src/workspace-plugin/screen.ts +34 -0
  342. package/src/workspace-plugin/templates/index.ts +17 -0
  343. package/src/workspace-plugin/templates/record-page-composition.ts +4051 -0
  344. package/src/workspace-plugin/templates/record-page-data.ts +523 -0
  345. package/src/workspace-plugin/templates/record-page-interalEventMappings.ts +35 -0
  346. package/src/workspace-plugin/templates/record-page-layoutModel.ts +51 -0
  347. package/src/workspace-plugin/templates/record-page-properties.ts +131 -0
  348. package/src/workspace-plugin/templates/record-page.ts +6 -0
  349. package/src/workspace-plugin.ts +574 -0
@@ -0,0 +1,1455 @@
1
+ export const APPLIES_TO_CATALOG_ITEM = 'item'
2
+ import {
3
+ type Record,
4
+ PropertyAccessShape,
5
+ IdentifierShape,
6
+ ts,
7
+ type Database,
8
+ TemplateExpressionShape,
9
+ Shape,
10
+ TemplateSpanShape,
11
+ type ObjectShape,
12
+ type Diagnostics,
13
+ CallExpressionShape,
14
+ type Transform,
15
+ isGUID,
16
+ } from '@servicenow/sdk-build-core'
17
+ import { NowIncludeShape } from '../now-include-plugin'
18
+ import { VariableTypeName, VARIABLE_TYPE_TO_NAME } from './variable-helper'
19
+
20
+ // Note: Database, ObjectShape, Diagnostics, and CallExpressionShape are imported for validateRequestedForVariable
21
+ // and other utility functions that validate variable configurations
22
+
23
+ /**
24
+ * Validates that there's at most one RequestedForVariable in a variables configuration (used only for VariableSet)
25
+ * @param variablesShape - The ObjectShape containing the variables configuration
26
+ * @param diagnostics - Diagnostics instance for reporting errors
27
+ * @param context - Context where the validation is being performed ('VariableSet')
28
+ * @returns True if validation passes, false otherwise
29
+ */
30
+ export function validateRequestedForVariable(
31
+ variablesShape: ObjectShape,
32
+ diagnostics: Diagnostics,
33
+ context: 'VariableSet' = 'VariableSet'
34
+ ): boolean {
35
+ let requestedForCount = 0
36
+ const entries = Array.from(variablesShape.entries())
37
+
38
+ for (const [, value] of entries) {
39
+ const callExpr = value.as(CallExpressionShape)
40
+ const calleeName = callExpr.getCallee()
41
+
42
+ if (calleeName === VariableTypeName.REQUESTED_FOR) {
43
+ requestedForCount++
44
+ if (requestedForCount > 1) {
45
+ diagnostics.error(
46
+ variablesShape.getOriginalNode(),
47
+ `Only one RequestedForVariable is allowed in a ${context}`
48
+ )
49
+ return false
50
+ }
51
+ }
52
+ }
53
+
54
+ return true
55
+ }
56
+
57
+ /**
58
+ * Combined validation for variable sets that performs multiple checks in a single loop:
59
+ * - At most one RequestedForVariable
60
+ * - Variable types not supported in multiRow variable sets
61
+ * @param variablesShape - The ObjectShape containing the variables configuration
62
+ * @param variableSetType - The type of the variable set ('singleRow' or 'multiRow')
63
+ * @param diagnostics - Diagnostics instance for reporting errors
64
+ * @param context - Context where the validation is being performed ('VariableSet')
65
+ * @returns True if validation passes, false otherwise
66
+ */
67
+ export function validateVariableSetVariables(
68
+ variablesShape: ObjectShape,
69
+ variableSetType: string,
70
+ diagnostics: Diagnostics,
71
+ context: 'VariableSet' = 'VariableSet'
72
+ ): boolean {
73
+ let requestedForCount = 0
74
+
75
+ // Only validate multiRow restrictions for multiRow variable sets
76
+ const isMultiRow = variableSetType === 'multiRow'
77
+ const unsupportedVariableTypes = isMultiRow
78
+ ? new Set([
79
+ VariableTypeName.ATTACHMENT,
80
+ VariableTypeName.BREAK,
81
+ VariableTypeName.CONTAINER_END,
82
+ VariableTypeName.CONTAINER_START,
83
+ VariableTypeName.CONTAINER_SPLIT,
84
+ VariableTypeName.HTML,
85
+ VariableTypeName.LABEL,
86
+ VariableTypeName.CUSTOM, // Macro
87
+ VariableTypeName.CUSTOM_WITH_LABEL, // Macro with label
88
+ VariableTypeName.RICH_TEXT_LABEL,
89
+ VariableTypeName.UI_PAGE,
90
+ ])
91
+ : null
92
+
93
+ const entries = Array.from(variablesShape.entries())
94
+
95
+ for (const [variableName, value] of entries) {
96
+ const callExpr = value.as(CallExpressionShape)
97
+ const calleeName = callExpr.getCallee()
98
+
99
+ // Check RequestedForVariable count
100
+ if (calleeName === VariableTypeName.REQUESTED_FOR) {
101
+ requestedForCount++
102
+ if (requestedForCount > 1) {
103
+ diagnostics.error(
104
+ variablesShape.getOriginalNode(),
105
+ `Only one RequestedForVariable is allowed in a ${context}`
106
+ )
107
+ return false
108
+ }
109
+ }
110
+
111
+ // Check multiRow variable type restrictions
112
+ if (isMultiRow && unsupportedVariableTypes && unsupportedVariableTypes.has(calleeName)) {
113
+ diagnostics.error(
114
+ value.getOriginalNode(),
115
+ `Variable type '${calleeName}' is not supported in multiRow variable sets. Variable '${variableName}' cannot be used.`
116
+ )
117
+ return false
118
+ }
119
+ }
120
+
121
+ return true
122
+ }
123
+
124
+ /**
125
+ * @deprecated Use validateVariableSetVariables instead
126
+ * Validates that certain variable types are not used in multiRow variable sets.
127
+ * MultiRow variable sets don't support certain display/layout variables.
128
+ * @param variablesShape - The ObjectShape containing the variables configuration
129
+ * @param variableSetType - The type of the variable set ('singleRow' or 'multiRow')
130
+ * @param diagnostics - Diagnostics instance for reporting errors
131
+ * @returns True if validation passes, false otherwise
132
+ */
133
+ export function validateVariableTypesForMultiRow(
134
+ variablesShape: ObjectShape,
135
+ variableSetType: string,
136
+ diagnostics: Diagnostics
137
+ ): boolean {
138
+ return validateVariableSetVariables(variablesShape, variableSetType, diagnostics, 'VariableSet')
139
+ }
140
+
141
+ /**
142
+ * Optimized validation that checks all RequestedForVariable constraints in a single pass:
143
+ * - At most one RequestedForVariable in direct variables
144
+ * - Not used in both direct variables and attached variable sets
145
+ * - Only one RequestedForVariable across all attached variable sets
146
+ * @param arg - The ObjectShape containing the catalog item/record producer configuration
147
+ * @param diagnostics - Diagnostics instance for reporting errors
148
+ * @param context - Context where the validation is being performed ('CatalogItem' or 'RecordProducer')
149
+ * @returns True if validation passes, false otherwise
150
+ */
151
+ export function validateRequestedForVariableConflict(
152
+ arg: ObjectShape,
153
+ diagnostics: Diagnostics,
154
+ context: 'CatalogItem' | 'RecordProducer'
155
+ ): boolean {
156
+ // Single pass through direct variables: count RequestedForVariable instances
157
+ let requestedForCountInDirectVariables = 0
158
+ if (arg.get('variables').isDefined()) {
159
+ const variablesConfig = arg.get('variables').asObject()
160
+ const entries = Array.from(variablesConfig.entries())
161
+
162
+ for (const [, value] of entries) {
163
+ const callExpr = value.as(CallExpressionShape)
164
+ const calleeName = callExpr.getCallee()
165
+
166
+ if (calleeName === 'RequestedForVariable') {
167
+ requestedForCountInDirectVariables++
168
+
169
+ // Validate at most one in direct variables
170
+ if (requestedForCountInDirectVariables > 1) {
171
+ diagnostics.error(
172
+ variablesConfig.getOriginalNode(),
173
+ `Only one RequestedForVariable is allowed in a ${context}`
174
+ )
175
+ return false
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ // Check for RequestedForVariable conflicts across variable sets and direct variables
182
+ if (arg.get('variableSets').isDefined()) {
183
+ const variableSets = arg.get('variableSets').ifArray()?.getElements() ?? []
184
+ let requestedForVariableSetCount = 0
185
+
186
+ for (const variableSet of variableSets) {
187
+ const varObj = variableSet.asObject()
188
+ if (varObj.get('variableSet').isRecord()) {
189
+ const vsRecord = varObj.get('variableSet').asRecord()
190
+ const variableSetRelRecords = vsRecord.flat().filter((r: Record) => r.getTable() === 'item_option_new')
191
+
192
+ if (variableSetRelRecords && variableSetRelRecords.length > 0) {
193
+ for (const variableRecord of variableSetRelRecords) {
194
+ const varType = variableRecord.get('type')?.getValue()
195
+ const varTypeName = VARIABLE_TYPE_TO_NAME[varType as string]
196
+
197
+ if (varTypeName === 'RequestedForVariable') {
198
+ requestedForVariableSetCount++
199
+
200
+ // If direct variables also have RequestedForVariable
201
+ if (requestedForCountInDirectVariables > 0) {
202
+ diagnostics.error(
203
+ arg.get('variables').getOriginalNode(),
204
+ `RequestedForVariable cannot be used in both ${context} variables and attached VariableSets. Remove RequestedForVariable from either the ${context} variables or the attached VariableSet.`
205
+ )
206
+ return false
207
+ }
208
+
209
+ // If multiple variable sets have RequestedForVariable
210
+ if (requestedForVariableSetCount > 1) {
211
+ diagnostics.error(
212
+ arg.get('variableSets').getOriginalNode(),
213
+ `RequestedForVariable cannot be used in multiple VariableSets attached to the same ${context}. Only one RequestedForVariable is allowed across all VariableSets.`
214
+ )
215
+ return false
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ return true
225
+ }
226
+
227
+ /**
228
+ * Validates that variable names (keys) in attached variable sets don't conflict with direct variable names
229
+ * @param arg - The ObjectShape containing the catalog item/record producer configuration
230
+ * @param diagnostics - Diagnostics instance for reporting errors
231
+ * @param context - Context where the validation is being performed ('CatalogItem' or 'RecordProducer')
232
+ * @returns True if validation passes, false otherwise
233
+ */
234
+ export function validateVariableNameConflicts(
235
+ arg: ObjectShape,
236
+ diagnostics: Diagnostics,
237
+ context: 'CatalogItem' | 'RecordProducer'
238
+ ): boolean {
239
+ // Collect all variable names from direct variables
240
+ const directVariableNames = new Set<string>()
241
+ if (arg.get('variables').isDefined()) {
242
+ const variablesConfig = arg.get('variables').asObject()
243
+ const entries = Array.from(variablesConfig.entries())
244
+
245
+ for (const [key] of entries) {
246
+ directVariableNames.add(key)
247
+ }
248
+ }
249
+
250
+ // If no direct variables, no conflict possible
251
+ if (directVariableNames.size === 0) {
252
+ return true
253
+ }
254
+
255
+ // Check for name conflicts in attached variable sets
256
+ if (arg.get('variableSets').isDefined()) {
257
+ const variableSets = arg.get('variableSets').ifArray()?.getElements() ?? []
258
+
259
+ for (const variableSet of variableSets) {
260
+ const varObj = variableSet.asObject()
261
+ if (varObj.get('variableSet').isRecord()) {
262
+ const vsRecord = varObj.get('variableSet').asRecord()
263
+ const variableSetRelRecords = vsRecord.flat().filter((r: Record) => r.getTable() === 'item_option_new')
264
+
265
+ if (variableSetRelRecords && variableSetRelRecords.length > 0) {
266
+ for (const variableRecord of variableSetRelRecords) {
267
+ const varName = variableRecord.get('name')?.getValue() as string
268
+
269
+ if (varName && directVariableNames.has(varName)) {
270
+ diagnostics.error(
271
+ arg.get('variables').getOriginalNode(),
272
+ `Variable name conflict: '${varName}' is defined in both ${context} variables and an attached VariableSet. Each variable must have a unique name across the ${context} and all attached VariableSets.`
273
+ )
274
+ return false
275
+ }
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ return true
283
+ }
284
+
285
+ /**
286
+ * Safely converts a value to a number with a default fallback
287
+ * Handles empty strings and undefined values
288
+ * @param value - The value to convert
289
+ * @param defaultValue - The default value to use if conversion fails or value is empty/undefined
290
+ * @returns The converted number or default value
291
+ */
292
+ export function convertToNumber(value: Shape, defaultValue: number = 0): number {
293
+ if (!value || value.isUndefined() || value.ifString()?.isEmpty()) {
294
+ return defaultValue
295
+ }
296
+ return value.toNumber()?.getValue() ?? defaultValue
297
+ }
298
+
299
+ export function getUITypeFromId(id: number): 'desktop' | 'mobileOrServicePortal' | 'all' {
300
+ switch (id) {
301
+ case 0:
302
+ return 'desktop'
303
+ case 1:
304
+ return 'mobileOrServicePortal'
305
+ case 10:
306
+ return 'all'
307
+ default:
308
+ throw Error('Invalid UI Type encountered, check XML data before transforming again.')
309
+ }
310
+ }
311
+
312
+ export function getUITypeId(value: string): number {
313
+ switch (value) {
314
+ case 'desktop':
315
+ return 0
316
+ case 'mobileOrServicePortal':
317
+ return 1
318
+ case 'all':
319
+ return 10
320
+ default:
321
+ return 0 // Default to desktop
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Mapping between ServiceNow variable set type database values and TypeScript enum values
327
+ */
328
+ const VariableSetTypeMapping = {
329
+ one_to_one: 'singleRow',
330
+ one_to_many: 'multiRow',
331
+ } as const
332
+
333
+ /**
334
+ * Converts ServiceNow variable set type database value to camelCase enum value
335
+ * @param dbValue - Database value ('one_to_one' or 'one_to_many')
336
+ * @returns TypeScript enum value ('singleRow' or 'multiRow')
337
+ */
338
+ export function getVariableSetTypeFromDb(dbValue: string): 'singleRow' | 'multiRow' {
339
+ const type = VariableSetTypeMapping[dbValue as keyof typeof VariableSetTypeMapping]
340
+ if (!type) {
341
+ return 'singleRow' // Default to singleRow if invalid
342
+ }
343
+ return type as 'singleRow' | 'multiRow'
344
+ }
345
+
346
+ /**
347
+ * Converts TypeScript enum value to ServiceNow variable set type database value
348
+ * @param value - TypeScript enum value ('singleRow' or 'multiRow')
349
+ * @returns Database value ('one_to_one' or 'one_to_many')
350
+ */
351
+ export function getVariableSetTypeToDb(value: string): string {
352
+ switch (value) {
353
+ case 'singleRow':
354
+ return 'one_to_one'
355
+ case 'multiRow':
356
+ return 'one_to_many'
357
+ default:
358
+ return 'one_to_one' // Default to one_to_one if invalid
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Mapping between ServiceNow redirect URL database values and TypeScript enum values
364
+ */
365
+ const RedirectUrlMapping = {
366
+ generated_record: 'generatedRecord',
367
+ catalog_home: 'catalogHomePage',
368
+ } as const
369
+
370
+ /**
371
+ * Converts ServiceNow redirect URL database value to camelCase enum value
372
+ * @param dbValue - Database value ('generated_record' or 'catalog_home')
373
+ * @returns TypeScript enum value ('generatedRecord' or 'catalogHomePage')
374
+ */
375
+ export function getRedirectUrlFromDb(dbValue: string): 'generatedRecord' | 'catalogHomePage' {
376
+ const type = RedirectUrlMapping[dbValue as keyof typeof RedirectUrlMapping]
377
+ if (!type) {
378
+ return 'generatedRecord' // Default to generatedRecord if invalid
379
+ }
380
+ return type as 'generatedRecord' | 'catalogHomePage'
381
+ }
382
+
383
+ /**
384
+ * Converts TypeScript enum value to ServiceNow redirect URL database value
385
+ * @param value - TypeScript enum value ('generatedRecord' or 'catalogHomePage')
386
+ * @returns Database value ('generated_record' or 'catalog_home')
387
+ */
388
+ export function getRedirectUrlToDb(value: string): string {
389
+ switch (value) {
390
+ case 'generatedRecord':
391
+ return 'generated_record'
392
+ case 'catalogHomePage':
393
+ return 'catalog_home'
394
+ default:
395
+ return 'generated_record' // Default to generated_record if invalid
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Mapping between ServiceNow fulfillment automation level database values and TypeScript enum values
401
+ */
402
+ const FulfillmentAutomationLevelMapping = {
403
+ unspecified: 'unspecified',
404
+ manual: 'manual',
405
+ semi_automated: 'semiAutomated',
406
+ fully_automated: 'fullyAutomated',
407
+ } as const
408
+
409
+ /**
410
+ * Converts ServiceNow fulfillment automation level database value to camelCase enum value
411
+ * @param dbValue - Database value ('unspecified', 'manual', 'semi_automated', or 'fully_automated')
412
+ * @returns TypeScript enum value ('unspecified', 'manual', 'semiAutomated', or 'fullyAutomated')
413
+ */
414
+ export function getFulfillmentAutomationLevelFromDb(
415
+ dbValue: string
416
+ ): 'unspecified' | 'manual' | 'semiAutomated' | 'fullyAutomated' {
417
+ const type = FulfillmentAutomationLevelMapping[dbValue as keyof typeof FulfillmentAutomationLevelMapping]
418
+ if (!type) {
419
+ return 'unspecified' // Default to unspecified if invalid
420
+ }
421
+ return type as 'unspecified' | 'manual' | 'semiAutomated' | 'fullyAutomated'
422
+ }
423
+
424
+ /**
425
+ * Converts TypeScript enum value to ServiceNow fulfillment automation level database value
426
+ * @param value - TypeScript enum value ('unspecified', 'manual', 'semiAutomated', or 'fullyAutomated')
427
+ * @returns Database value ('unspecified', 'manual', 'semi_automated', or 'fully_automated')
428
+ */
429
+ export function getFulfillmentAutomationLevelToDb(value: string): string {
430
+ switch (value) {
431
+ case 'unspecified':
432
+ return 'unspecified'
433
+ case 'manual':
434
+ return 'manual'
435
+ case 'semiAutomated':
436
+ return 'semi_automated'
437
+ case 'fullyAutomated':
438
+ return 'fully_automated'
439
+ default:
440
+ return 'unspecified' // Default to unspecified if invalid
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Mapping between ServiceNow availability database values and TypeScript enum values
446
+ */
447
+ const AvailabilityMapping = {
448
+ on_desktop: 'desktopOnly',
449
+ on_mobile: 'mobileOnly',
450
+ on_both: 'both',
451
+ } as const
452
+
453
+ /**
454
+ * Converts ServiceNow availability database value to camelCase enum value
455
+ * @param dbValue - Database value ('on_desktop', 'on_mobile', or 'on_both')
456
+ * @returns TypeScript enum value ('desktopOnly', 'mobileOnly', or 'both')
457
+ */
458
+ export function getAvailabilityFromDb(dbValue: string): 'desktopOnly' | 'mobileOnly' | 'both' {
459
+ const type = AvailabilityMapping[dbValue as keyof typeof AvailabilityMapping]
460
+ if (!type) {
461
+ return 'desktopOnly' // Default to desktopOnly if invalid
462
+ }
463
+ return type as 'desktopOnly' | 'mobileOnly' | 'both'
464
+ }
465
+
466
+ /**
467
+ * Converts TypeScript enum value to ServiceNow availability database value
468
+ * @param value - TypeScript enum value ('desktopOnly', 'mobileOnly', or 'both')
469
+ * @returns Database value ('on_desktop', 'on_mobile', or 'on_both')
470
+ */
471
+ export function getAvailabilityToDb(value: string): string {
472
+ switch (value) {
473
+ case 'desktopOnly':
474
+ return 'on_desktop'
475
+ case 'mobileOnly':
476
+ return 'on_mobile'
477
+ case 'both':
478
+ return 'on_both'
479
+ default:
480
+ return 'on_desktop' // Default to on_desktop if invalid
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Mapping between ServiceNow mobile picture type database values and TypeScript enum values
486
+ */
487
+ const MobilePictureTypeMapping = {
488
+ use_desktop_picture: 'desktopPicture',
489
+ use_mobile_picture: 'mobilePicture',
490
+ use_no_picture: 'noPicture',
491
+ } as const
492
+
493
+ /**
494
+ * Converts ServiceNow mobile picture type database value to camelCase enum value
495
+ * @param dbValue - Database value ('use_desktop_picture', 'use_mobile_picture', or 'use_no_picture')
496
+ * @returns TypeScript enum value ('desktopPicture', 'mobilePicture', or 'noPicture')
497
+ */
498
+ export function getMobilePictureTypeFromDb(dbValue: string): 'desktopPicture' | 'mobilePicture' | 'noPicture' {
499
+ const type = MobilePictureTypeMapping[dbValue as keyof typeof MobilePictureTypeMapping]
500
+ if (!type) {
501
+ return 'desktopPicture' // Default to desktopPicture if invalid
502
+ }
503
+ return type as 'desktopPicture' | 'mobilePicture' | 'noPicture'
504
+ }
505
+
506
+ /**
507
+ * Converts TypeScript enum value to ServiceNow mobile picture type database value
508
+ * @param value - TypeScript enum value ('desktopPicture', 'mobilePicture', or 'noPicture')
509
+ * @returns Database value ('use_desktop_picture', 'use_mobile_picture', or 'use_no_picture')
510
+ */
511
+ export function getMobilePictureTypeToDb(value: string): string {
512
+ switch (value) {
513
+ case 'desktopPicture':
514
+ return 'use_desktop_picture'
515
+ case 'mobilePicture':
516
+ return 'use_mobile_picture'
517
+ case 'noPicture':
518
+ return 'use_no_picture'
519
+ default:
520
+ return 'use_desktop_picture' // Default to use_desktop_picture if invalid
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Mapping between ServiceNow value action database values and TypeScript enum values
526
+ */
527
+ const ValueActionMapping = {
528
+ clear_value: 'clearValue',
529
+ set_value: 'setValue',
530
+ } as const
531
+
532
+ /**
533
+ * Converts ServiceNow value action database value to camelCase enum value
534
+ * @param dbValue - Database value ('clear_value', 'set_value', or 'ignore')
535
+ * @returns TypeScript enum value ('clearValue', 'setValue'), or undefined if 'ignore'
536
+ */
537
+ export function getValueActionFromDb(dbValue: string): 'clearValue' | 'setValue' | undefined {
538
+ if (dbValue === 'ignore') {
539
+ return undefined
540
+ }
541
+ const type = ValueActionMapping[dbValue as keyof typeof ValueActionMapping]
542
+ if (!type) {
543
+ return undefined // Return undefined for 'ignore' or invalid values
544
+ }
545
+ return type as 'clearValue' | 'setValue'
546
+ }
547
+
548
+ /**
549
+ * Converts TypeScript enum value to ServiceNow value action database value
550
+ * @param value - TypeScript enum value ('clearValue', 'setValue', or 'ignore')
551
+ * @returns Database value ('clear_value', 'set_value', or 'ignore')
552
+ */
553
+ export function getValueActionToDb(value: string): string {
554
+ switch (value) {
555
+ case 'clearValue':
556
+ return 'clear_value'
557
+ case 'setValue':
558
+ return 'set_value'
559
+ case 'ignore':
560
+ return 'ignore'
561
+ default:
562
+ return 'ignore' // Default to ignore if invalid
563
+ }
564
+ }
565
+
566
+ const VisibilityTypeMapping = {
567
+ Always: 1,
568
+ Bundle: 2,
569
+ Standalone: 3,
570
+ } as const
571
+
572
+ export function getVisibilityFromId(id: number): 'Always' | 'Bundle' | 'Standalone' {
573
+ const entry = Object.entries(VisibilityTypeMapping).find(([, value]) => value === id)
574
+ if (!entry) {
575
+ return 'Always' // Default to Always if invalid
576
+ }
577
+ return entry[0] as 'Always' | 'Bundle' | 'Standalone'
578
+ }
579
+
580
+ export function getVisibilityId(value: string): number {
581
+ if (value in VisibilityTypeMapping) {
582
+ return VisibilityTypeMapping[value as keyof typeof VisibilityTypeMapping]
583
+ }
584
+ return 1 // Default to Always
585
+ }
586
+
587
+ /**
588
+ * Finds a variable record from a catalog item's related records by variable name
589
+ * @param catalogItemRecord - The catalog item Record to search in
590
+ * @param variableName - The name of the variable to find
591
+ * @returns The variable record if found, undefined otherwise
592
+ */
593
+ export function findNameInParent(catalogItemRecord: Record, variableName: string) {
594
+ const relatedRecords = catalogItemRecord.flat()
595
+ return relatedRecords.find(
596
+ (record: Record) =>
597
+ record.getTable() === 'item_option_new' && record.get('name')?.ifString()?.getValue() === variableName
598
+ )
599
+ }
600
+
601
+ /**
602
+ * Resolves a variable ID from a parent record and variable name with fallback logic
603
+ * @param parentRecord - The resolved parent record (may or may not be a Record instance)
604
+ * @param variableName - The name of the variable to resolve
605
+ * @returns The formatted variable ID with IO: prefix
606
+ */
607
+ export function resolveVariableId(parentRecord: Record, variableName: string): string {
608
+ if (parentRecord.isRecord()) {
609
+ const variableRecord = findNameInParent(parentRecord, variableName)
610
+ if (variableRecord) {
611
+ return `IO:${variableRecord.getId().getValue()}`
612
+ }
613
+ }
614
+ // Fallback to string value if variable record not found or parentRecord is not a Record
615
+ return `IO:${variableName}`
616
+ }
617
+
618
+ /**
619
+ * Resolves catalog item and variable set references based on the applies_to field
620
+ * @param record - The source record containing catalog_item/cat_item and variable_set fields
621
+ * @param database - The database instance for looking up records
622
+ * @returns Object with catalogItemReference and variableSetReference
623
+ */
624
+ export function resolveCatalogReferences(
625
+ record: Record,
626
+ database: Database
627
+ ): {
628
+ catalogItemReference: IdentifierShape | string | undefined
629
+ variableSetReference: IdentifierShape | string | undefined
630
+ } {
631
+ const appliesTo = record.get('applies_to')?.ifString()?.getValue() || APPLIES_TO_CATALOG_ITEM
632
+
633
+ let catalogItemReference: IdentifierShape | string | undefined
634
+ let variableSetReference: IdentifierShape | string | undefined
635
+
636
+ const targetRecord = getTargetRecord(record, database)
637
+
638
+ if (targetRecord?.isRecord()) {
639
+ const identifier = parentIdentifier(targetRecord)
640
+ const recordId = targetRecord.getId().getValue()
641
+
642
+ if (appliesTo === APPLIES_TO_CATALOG_ITEM) {
643
+ catalogItemReference = identifier || recordId
644
+ } else if (appliesTo === 'set') {
645
+ variableSetReference = identifier || recordId
646
+ }
647
+ } else {
648
+ if (appliesTo === APPLIES_TO_CATALOG_ITEM) {
649
+ catalogItemReference =
650
+ record.get('catalog_item')?.ifString()?.getValue() || record.get('cat_item')?.ifString()?.getValue()
651
+ } else if (appliesTo === 'set') {
652
+ variableSetReference = record.get('variable_set')?.ifString()?.getValue()
653
+ }
654
+ }
655
+
656
+ return { catalogItemReference, variableSetReference }
657
+ }
658
+
659
+ /**
660
+ * Resolves a record reference (catalog item or variable set) to an IdentifierShape
661
+ * @param recordId - The sys_id of the record to resolve
662
+ * @param tableName - The table name ('sc_cat_item' or 'item_option_new_set')
663
+ * @param database - The database instance for looking up records
664
+ * @returns IdentifierShape if the record exists and has a name, otherwise the original sys_id or undefined
665
+ */
666
+ export function resolveRecordReference(
667
+ recordId: string | undefined,
668
+ tableName: 'sc_cat_item' | 'item_option_new_set',
669
+ database: Database
670
+ ): IdentifierShape | string | undefined {
671
+ if (!recordId || recordId.trim() === '') {
672
+ return undefined
673
+ }
674
+
675
+ const record = database.get(tableName, recordId)
676
+ if (record?.isRecord()) {
677
+ const identifier = parentIdentifier(record)
678
+ return identifier || recordId
679
+ }
680
+
681
+ return recordId
682
+ }
683
+
684
+ export function parentIdentifier(parentRecord: Record): IdentifierShape | undefined {
685
+ // First try to get the name from the AST (for Fluent → platform transformation)
686
+ if (parentRecord.getCreator()) {
687
+ const parentName = parentRecord
688
+ ?.getOriginalNode()
689
+ ?.getFirstAncestorByKind(ts.SyntaxKind.VariableDeclaration)
690
+ ?.getName()
691
+
692
+ if (parentName) {
693
+ return new IdentifierShape({
694
+ source: parentRecord.getOriginalNode(),
695
+ name: parentName,
696
+ value: parentRecord,
697
+ })
698
+ }
699
+ }
700
+
701
+ // Fallback: generate identifier name from record's name field (for platform → Fluent transformation)
702
+ const recordName =
703
+ parentRecord.get('sys_class_name')?.ifString()?.getValue() === 'item_option_new_set'
704
+ ? parentRecord.get('title')?.ifString()?.getValue()
705
+ : parentRecord.get('name')?.ifString()?.getValue()
706
+ if (recordName) {
707
+ const identifierName = toValidIdentifier(recordName)
708
+ return new IdentifierShape({
709
+ source: parentRecord,
710
+ name: identifierName,
711
+ value: parentRecord,
712
+ })
713
+ }
714
+
715
+ return undefined
716
+ }
717
+
718
+ /**
719
+ * Creates a PropertyAccessShape for a catalog variable from an item record
720
+ * @param itemId - The item_option_new record
721
+ * @param source - The source record for the PropertyAccessShape
722
+ * @param parent - The parent record from the record
723
+ * @returns PropertyAccessShape representing catalogItem.variables.variableName
724
+ */
725
+ export function createVariablePropertyAccess(
726
+ variableRecord: Record,
727
+ source: Record,
728
+ parent: Record
729
+ ): PropertyAccessShape | undefined {
730
+ if (!variableRecord.getCreator()) {
731
+ return undefined
732
+ }
733
+ // Extract the catalog item variable name from the AST
734
+ const parentName = variableRecord
735
+ ?.getOriginalNode()
736
+ ?.getFirstAncestorByKind(ts.SyntaxKind.VariableDeclaration)
737
+ ?.getName()
738
+
739
+ if (!parentName) {
740
+ return undefined
741
+ }
742
+ // Extract the variable property name
743
+ const varName = variableRecord?.getOriginalNode()?.getParent()?.asKind(ts.SyntaxKind.PropertyAssignment)?.getName()
744
+
745
+ if (!varName) {
746
+ return undefined
747
+ }
748
+
749
+ // Create identifier for the catalog item using parent's original node for proper import tracking
750
+ const parentIdentifier = new IdentifierShape({
751
+ source: parent.getOriginalNode(),
752
+ name: parentName,
753
+ value: parent,
754
+ })
755
+
756
+ // Create and return the property access shape
757
+ return new PropertyAccessShape({
758
+ source: source,
759
+ elements: [parentIdentifier, 'variables', varName],
760
+ })
761
+ }
762
+
763
+ /**
764
+ * Resolves the appropriate record for catalog item or variable set operations
765
+ * @param record - The source record containing catalog_item/cat_item or variable_set references
766
+ * @param database - The database instance for looking up records by ID
767
+ * @returns The resolved record (sc_cat_item or item_option_new_set) or undefined if not found
768
+ */
769
+ export function getTargetRecord(record: Record, database: Database): Record | undefined {
770
+ if (record.get('applies_to')?.ifString()?.getValue() === APPLIES_TO_CATALOG_ITEM) {
771
+ // Try catalog_item field (used in UI policies)
772
+ if (record.get('catalog_item')?.isDefined() && record.get('catalog_item')?.isRecord()) {
773
+ return record.get('catalog_item').asRecord()
774
+ }
775
+ // Try cat_item field (used in client scripts)
776
+ else if (record.get('cat_item')?.isDefined() && record.get('cat_item')?.isRecord()) {
777
+ return record.get('cat_item').asRecord()
778
+ }
779
+
780
+ // Try catalog_item string reference (used in UI policies)
781
+ else if (record.get('catalog_item')?.isDefined() && !record.get('catalog_item')?.ifString()?.isEmpty()) {
782
+ return database.get('sc_cat_item', record.get('catalog_item').asString()?.getValue())
783
+ }
784
+ // Try cat_item string reference (used in client scripts)
785
+ else if (record.get('cat_item')?.isDefined() && !record.get('cat_item')?.ifString()?.isEmpty()) {
786
+ return database.get('sc_cat_item', record.get('cat_item').asString()?.getValue())
787
+ }
788
+ //Fallback to sys_id
789
+ return undefined
790
+ } else {
791
+ // Handle variable set record reference
792
+ if (record.get('variable_set')?.isRecord()) {
793
+ return record.get('variable_set').asRecord()
794
+ }
795
+ // Handle variable set string reference
796
+ else if (!record.get('variable_set')?.ifString()?.isEmpty()) {
797
+ return database.get('item_option_new_set', record.get('variable_set').asString()?.getValue())
798
+ }
799
+ return undefined
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Resolves a variable name from a PropertyAccessShape
805
+ * @param variableNameShape - The shape containing the property access
806
+ * @returns An object containing the parent record and variable name
807
+ */
808
+ export function resolveVariableAccess(variableNameShape: PropertyAccessShape):
809
+ | {
810
+ parentRecord: Record
811
+ variableName: string
812
+ }
813
+ | undefined {
814
+ const propertyAccess = variableNameShape
815
+ const variableName = propertyAccess.getLastElement().getName()
816
+ const parentIdentifier = propertyAccess.getElements()[0]
817
+
818
+ try {
819
+ const resolved = parentIdentifier.resolve(true)
820
+ if (!resolved || !resolved.isRecord()) {
821
+ return undefined
822
+ }
823
+ const parentRecord = resolved.asRecord()
824
+ return { parentRecord, variableName }
825
+ } catch (error) {
826
+ // Resolution failed, return undefined to allow fallback
827
+ return undefined
828
+ }
829
+ }
830
+ /**
831
+ * Processes a catalog condition template expression
832
+ * @param conditionShape - The shape containing the catalog condition
833
+ * @param appliesTo - Optional appliesTo value for validation
834
+ * @param variableSetShape - Optional variable set shape for validation
835
+ * @param catalogItemShape - Optional catalog item shape for validation
836
+ * @param diagnostics - Optional diagnostics instance for reporting validation errors
837
+ * @returns The processed condition string with resolved variable references
838
+ */
839
+ export function processCatalogCondition(
840
+ conditionShape: Shape<unknown>,
841
+ appliesTo?: 'set' | 'item',
842
+ variableSetShape?: Shape<unknown>,
843
+ catalogItemShape?: Shape<unknown>,
844
+ diagnostics?: Diagnostics
845
+ ): string | undefined {
846
+ // Handle template expressions with variable references
847
+ if (conditionShape.is(TemplateExpressionShape)) {
848
+ const templateExpr = conditionShape.as(TemplateExpressionShape)
849
+ let result = templateExpr.getLiteralText()
850
+
851
+ // Process each span (interpolated expression)
852
+ for (const span of templateExpr.getSpans()) {
853
+ const expr = span.getExpression()
854
+
855
+ // Handle property access (e.g., catalogItem.variables.name)
856
+ if (expr.is(PropertyAccessShape)) {
857
+ const variableAccess = resolveVariableAccess(expr.as(PropertyAccessShape))
858
+ if (variableAccess) {
859
+ const variableId = resolveVariableId(variableAccess.parentRecord, variableAccess.variableName)
860
+ result += variableId
861
+
862
+ // Validate variable belongs to target based on appliesTo
863
+ const targetShape = appliesTo === 'set' ? variableSetShape : catalogItemShape
864
+ if (appliesTo && targetShape?.is(IdentifierShape)) {
865
+ validateVariableBelongsToTarget(
866
+ targetShape.as(IdentifierShape),
867
+ variableAccess.parentRecord,
868
+ appliesTo,
869
+ diagnostics
870
+ )
871
+ }
872
+ } else {
873
+ result += expr.toString().getValue()
874
+ }
875
+ } else {
876
+ // Fallback to string value
877
+ result += expr.toString().getValue()
878
+ }
879
+
880
+ result += span.getLiteralText()
881
+ }
882
+
883
+ return result
884
+ } else {
885
+ // Handle plain string
886
+ if (conditionShape.ifString()?.isEmpty()) {
887
+ return undefined
888
+ }
889
+
890
+ const conditionValue = conditionShape.toString()?.getValue()
891
+ if (!conditionValue) {
892
+ return undefined
893
+ }
894
+
895
+ // Add IO: prefixes back for XML format
896
+ // Handle patterns like: c34b1842b7321010e54deb56ee11a92b=21^ORc34b1842b7321010e54deb56ee11a92b=18^...
897
+ // Should become: IO:c34b1842b7321010e54deb56ee11a92b=21^ORIO:c34b1842b7321010e54deb56ee11a92b=18^...
898
+ // Match the correct ServiceNow catalog condition format
899
+ return conditionValue
900
+ .replace(/^(?!IO:)/, 'IO:') // Add IO: if not already present
901
+ .replace(/\^OR(?!IO:)/g, '^ORIO:') // Convert ^OR to ^ORIO: (but not if already ^ORIO:)
902
+ .replace(/\^NQ(?!IO:)/g, '^NQIO:') // Convert ^NQ to ^NQIO: (but not if already ^NQIO:)
903
+ .replace(/\^(?!(ORIO:|NQIO:|EQ))/g, '^IO:') // Add IO: after ^ except for ORIO:, NQIO:, EQ
904
+ .replace(/(?<!\^EQ)$/, '^EQ') // Add ^EQ at end if not already present
905
+ }
906
+ }
907
+
908
+ /**
909
+ * Represents the result of parsing a single catalog condition part
910
+ */
911
+ interface ParsedConditionPart {
912
+ logicalOperator: string
913
+ processedContent: string | PropertyAccessShape
914
+ operatorSuffix: string
915
+ hasPropertyAccess: boolean
916
+ }
917
+
918
+ /**
919
+ * Parses a single catalog condition part and extracts logical operators and variable references
920
+ * @param part - The condition part to parse
921
+ * @param record - The source record
922
+ * @param database - The database instance for looking up records
923
+ * @returns Parsed condition part with extracted components
924
+ */
925
+ export function parseConditionPart(part: string, record: Record, database: Database): ParsedConditionPart | undefined {
926
+ // Skip empty parts
927
+ if (!part.trim()) {
928
+ return undefined
929
+ }
930
+
931
+ // Check for logical operators at the beginning (only OR and NQ are valid prefixes)
932
+ const logicalOperatorMatch = part.match(/^(OR|NQ)/)
933
+ const logicalOperator: string = logicalOperatorMatch?.[1] || ''
934
+ const remainingPart = logicalOperator ? part.substring(logicalOperator.length) : part
935
+
936
+ // Handle different possible ID formats
937
+ let conditionVarId = ''
938
+ let operatorSuffix = ''
939
+
940
+ if (remainingPart.length >= 32) {
941
+ // Try standard 32-character ID first
942
+ const potentialId = remainingPart.substring(0, 32)
943
+ if (isGUID(potentialId)) {
944
+ conditionVarId = potentialId
945
+ operatorSuffix = remainingPart.substring(32)
946
+ } else {
947
+ // Look for other patterns like variable names
948
+ const varMatch = remainingPart.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(.*)/)
949
+ if (varMatch?.[1]) {
950
+ conditionVarId = varMatch[1]
951
+ operatorSuffix = varMatch[2] || ''
952
+ }
953
+ }
954
+ }
955
+
956
+ const conditionVarRecord = conditionVarId.length === 32 ? database.get('item_option_new', conditionVarId) : null
957
+ const appliesToRecord = getTargetRecord(record, database)
958
+
959
+ if (conditionVarRecord && conditionVarId.length === 32 && appliesToRecord) {
960
+ const propAccess = createVariablePropertyAccess(conditionVarRecord, record, appliesToRecord)
961
+ if (propAccess) {
962
+ return {
963
+ logicalOperator,
964
+ processedContent: propAccess,
965
+ operatorSuffix,
966
+ hasPropertyAccess: true,
967
+ }
968
+ }
969
+ }
970
+
971
+ // Fallback: treat as literal text if no variable found
972
+ // Use remainingPart (without logical operator) to avoid duplicating the OR/NQ prefix
973
+ return {
974
+ logicalOperator,
975
+ processedContent: remainingPart,
976
+ operatorSuffix: '',
977
+ hasPropertyAccess: false,
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Processes catalog conditions and converts them to either a TemplateExpressionShape or a string
983
+ * @param record - The record containing the catalog conditions
984
+ * @param database - The database instance for looking up records
985
+ * @returns The processed conditions as a TemplateExpressionShape or string, or undefined if no conditions
986
+ */
987
+ export function processCatalogConditionsToShape(
988
+ record: Record,
989
+ database: Database
990
+ ): TemplateExpressionShape | string | undefined {
991
+ let conditionsValue = record.get('catalog_conditions')?.ifString()?.getValue()
992
+ if (!conditionsValue) {
993
+ return undefined
994
+ }
995
+
996
+ // Handle the real ServiceNow catalog condition format
997
+ // Real format: ^ORIO:OR needs to become ^OR (not ^OROR!)
998
+ conditionsValue = conditionsValue
999
+ .replace(/\^ORIO:/g, '^OR') // Handle ^ORIO: -> ^OR
1000
+ .replace(/\^NQIO:/g, '^NQ') // Handle ^NQIO: -> ^NQ
1001
+ .replace(/IO:/g, '') // Remove all remaining IO: prefixes
1002
+ .replace(/(?<!\^EQ)$/, '^EQ') // Add ^EQ at end if not already present
1003
+
1004
+ // Don't process ^OR again since we've already handled ^ORIO: above
1005
+ if (!conditionsValue) {
1006
+ return undefined
1007
+ }
1008
+
1009
+ const conditionParts = conditionsValue.split('^')
1010
+ let hasPropertyAccess = false
1011
+ const processedParts: (string | PropertyAccessShape)[] = []
1012
+
1013
+ conditionParts.forEach((part, index) => {
1014
+ const parsed = parseConditionPart(part, record, database)
1015
+ if (!parsed) {
1016
+ return // Skip empty parts
1017
+ }
1018
+
1019
+ if (parsed.hasPropertyAccess) {
1020
+ hasPropertyAccess = true
1021
+ }
1022
+
1023
+ // Build the complete part string with logical operator and suffix
1024
+ let partString = ''
1025
+
1026
+ // Add logical operator prefix if present
1027
+ if (parsed.logicalOperator) {
1028
+ partString += parsed.logicalOperator
1029
+ }
1030
+
1031
+ // Add the main content (property access or literal)
1032
+ if (typeof parsed.processedContent === 'string') {
1033
+ partString += parsed.processedContent
1034
+ // Add operator suffix if present
1035
+ if (parsed.operatorSuffix) {
1036
+ partString += parsed.operatorSuffix
1037
+ }
1038
+ processedParts.push(partString)
1039
+ } else {
1040
+ // For property access, push logical operator prefix as literal text first
1041
+ if (partString) {
1042
+ processedParts.push(partString)
1043
+ }
1044
+ processedParts.push(parsed.processedContent)
1045
+ // Push operator suffix as literal text after the property access
1046
+ if (parsed.operatorSuffix) {
1047
+ processedParts.push(parsed.operatorSuffix)
1048
+ }
1049
+ }
1050
+
1051
+ // Add separator between parts (but not after last)
1052
+ if (index < conditionParts.length - 1) {
1053
+ processedParts.push('^')
1054
+ }
1055
+ })
1056
+
1057
+ if (!hasPropertyAccess) {
1058
+ return processedParts.join('')
1059
+ }
1060
+
1061
+ // Build template expression
1062
+ const spans: TemplateSpanShape[] = []
1063
+ let currentIndex = 0
1064
+
1065
+ for (const part of processedParts) {
1066
+ if (part instanceof PropertyAccessShape) {
1067
+ const nextPropIndex = processedParts.findIndex(
1068
+ (p, idx) => idx > currentIndex && p instanceof PropertyAccessShape
1069
+ )
1070
+ const endIndex = nextPropIndex === -1 ? processedParts.length : nextPropIndex
1071
+ const literalText = processedParts.slice(currentIndex + 1, endIndex).join('')
1072
+
1073
+ spans.push(
1074
+ new TemplateSpanShape({
1075
+ source: record,
1076
+ expression: part,
1077
+ literalText,
1078
+ })
1079
+ )
1080
+ }
1081
+ currentIndex++
1082
+ }
1083
+
1084
+ const firstPropIndex = processedParts.findIndex((p) => p instanceof PropertyAccessShape)
1085
+ const initialLiteral = processedParts.slice(0, firstPropIndex).join('')
1086
+
1087
+ return new TemplateExpressionShape({
1088
+ source: record,
1089
+ literalText: initialLiteral,
1090
+ spans,
1091
+ })
1092
+ }
1093
+
1094
+ /**
1095
+ * Converts a title to a valid ServiceNow internal name (snake_case)
1096
+ * @param title - The title to convert
1097
+ * @returns Snake_case internal name
1098
+ */
1099
+ export function convertTitleToInternalName(title: string): string {
1100
+ return title
1101
+ .trim()
1102
+ .toLowerCase()
1103
+ .replace(/[^a-z0-9]+/g, '_')
1104
+ .replace(/^_+|_+$/g, '')
1105
+ }
1106
+
1107
+ /**
1108
+ * Validation constants for internal names
1109
+ */
1110
+ export const INTERNAL_NAME_REGEX = /^[a-z][a-z0-9_]*$/
1111
+ export const INTERNAL_NAME_MAX_LENGTH = 80
1112
+
1113
+ /**
1114
+ * Validates an internal name for ServiceNow compatibility
1115
+ * @param internalName - The internal name to validate
1116
+ * @returns Validation result with error message if invalid
1117
+ */
1118
+ export function validateInternalName(internalName: string): {
1119
+ valid: boolean
1120
+ error?: string
1121
+ } {
1122
+ if (!INTERNAL_NAME_REGEX.test(internalName)) {
1123
+ return {
1124
+ valid: false,
1125
+ error:
1126
+ 'VariableSet internalName must start with a lowercase letter and contain only lowercase letters, numbers, and underscores (snake_case format). ' +
1127
+ 'No spaces, hyphens, or uppercase letters allowed.',
1128
+ }
1129
+ }
1130
+
1131
+ if (internalName.length > INTERNAL_NAME_MAX_LENGTH) {
1132
+ return {
1133
+ valid: false,
1134
+ error:
1135
+ `VariableSet internalName must be ${INTERNAL_NAME_MAX_LENGTH} characters or less. ` +
1136
+ `Current length: ${internalName.length}`,
1137
+ }
1138
+ }
1139
+
1140
+ return { valid: true }
1141
+ }
1142
+
1143
+ /**
1144
+ * Converts role values (arrays or Role records) to comma-separated string for ServiceNow storage
1145
+ * Handles both string arrays and Role record references
1146
+ * @param v - Shape value containing role data
1147
+ * @returns Comma-separated role string or undefined
1148
+ */
1149
+ export function convertRolesToString(v: Shape): string | undefined {
1150
+ return v
1151
+ .ifArray()
1152
+ ?.pipe((r) =>
1153
+ Array.from(
1154
+ new Set(r.getElements().map((role) => role.ifRecord()?.get('name').getValue() ?? role.getValue()))
1155
+ ).join(',')
1156
+ )
1157
+ }
1158
+
1159
+ /**
1160
+ * Parses comma-separated string into an array
1161
+ * @param string - Comma-separated string (e.g., "admin,itil")
1162
+ * @returns Array of trimmed strings
1163
+ */
1164
+ export function parseString(commaSeparatedString: Shape): string[] {
1165
+ if (commaSeparatedString.isUndefined() || commaSeparatedString.toString().isEmpty()) {
1166
+ return []
1167
+ }
1168
+ return commaSeparatedString
1169
+ .toString()
1170
+ .getValue()
1171
+ .split(',')
1172
+ .map((s) => s.trim())
1173
+ .filter(Boolean)
1174
+ }
1175
+ /**
1176
+ * Checks if the record should be written as a call expression
1177
+ * @param record - The record to check
1178
+ * @returns True if the record should be written as a call expression, false otherwise
1179
+ */
1180
+ export function shouldWriteAsCallExpression(record: Record): boolean {
1181
+ const originalSource = record.getOriginalSource()
1182
+ return ts.Node.isNode(originalSource) && originalSource.isKind(ts.SyntaxKind.CallExpression)
1183
+ }
1184
+
1185
+ /**
1186
+ * Converts a name to a valid JavaScript identifier
1187
+ * Examples:
1188
+ * "Backend item" -> "backendItem"
1189
+ * "Developer Workstation" -> "developerWorkstation"
1190
+ * "My-Special_Item 123" -> "mySpecialItem123"
1191
+ */
1192
+ export function toValidIdentifier(name: string): string {
1193
+ return (
1194
+ name
1195
+ .trim()
1196
+ // Replace non-alphanumeric characters with spaces
1197
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
1198
+ // Convert to camelCase
1199
+ .split(' ')
1200
+ .filter(Boolean)
1201
+ .map((word, index) => {
1202
+ const lowerWord = word.toLowerCase()
1203
+ return index === 0 ? lowerWord : lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1)
1204
+ })
1205
+ .join('')
1206
+ // Ensure it doesn't start with a number
1207
+ .replace(/^[0-9]/, '_$&')
1208
+ )
1209
+ }
1210
+
1211
+ /**
1212
+ * Default delivery time value used when no delivery time is specified
1213
+ */
1214
+ export const DEFAULT_DELIVERY_TIME = '1970-01-01 00:00:00'
1215
+
1216
+ /**
1217
+ * Creates a NowIncludeShape with a custom file path suffix for script separation
1218
+ */
1219
+ export async function createScript(
1220
+ record: Record,
1221
+ scriptContent: string | Shape,
1222
+ transform: Transform,
1223
+ suffix: string
1224
+ ): Promise<NowIncludeShape> {
1225
+ const baseName = await transform.getUpdateName(record)
1226
+ const content = scriptContent instanceof Shape ? scriptContent.toString().getValue() : scriptContent
1227
+ return new NowIncludeShape({
1228
+ source: record,
1229
+ path: `./${baseName}-${suffix}.js`,
1230
+ includedText: content,
1231
+ })
1232
+ }
1233
+
1234
+ /**
1235
+ * Default script
1236
+ */
1237
+ export const defaultScript = `/** This script is executed before the Record is generated
1238
+ * \`current\`- GlideRecord produced by Record Producer
1239
+ * Don't use \`current.update()\` or \`current.insert()\` as the record is generated by Record Producer
1240
+ * Don't use \`current.setValue('sys_class_name', 'xxx')\` as this will trigger reparent flow and can cause data loss
1241
+ * Avoid \`current.setAbortAction()\` and generate a separate record
1242
+ * Use \`producer.var1\` to access variables
1243
+ */`
1244
+
1245
+ /**
1246
+ * Default post insert script
1247
+ */
1248
+ export const defaultpostInsertScript = `/**
1249
+ * This script is executed after the record is generated.
1250
+ * \`current\` Is the GlideRecord produced by Record Producer. Use \`current.update()\` to update the record
1251
+ * To access the variables, use \`producer.var1\` where var1 is the name of the variable
1252
+ * To access the Record Producer use \`cat_item\`
1253
+ */`
1254
+
1255
+ /**
1256
+ * Default save script
1257
+ */
1258
+ export const defaultSaveScript = `/**
1259
+ * This script is executed at every step save in Catalog Builder.
1260
+ * This script is executed before \`Script\` is executed.
1261
+ * \`current\` Is the GlideRecord produced by Record Producer.
1262
+ * To access the variables, use \`producer.var1\` where var1 is the name of the variable
1263
+ * To access the Record Producer use \`cat_item\`
1264
+ */`
1265
+
1266
+ /**
1267
+ * Validates that a variable belongs to a target record (variable set or catalog item)
1268
+ * Reports diagnostic error if validation fails
1269
+ * @param targetShape - The IdentifierShape for the target reference (variable set or catalog item)
1270
+ * @param parentRecord - The parent record where the variable is defined
1271
+ * @param appliesTo - Whether the validation is for 'set' (variable set) or 'item' (catalog item)
1272
+ * @param diagnostics - Optional diagnostics instance for reporting errors
1273
+ * @returns true if the variable belongs to the target, false otherwise
1274
+ */
1275
+ export function validateVariableBelongsToTarget(
1276
+ targetShape: IdentifierShape,
1277
+ parentRecord: Record,
1278
+ appliesTo: 'set' | 'item',
1279
+ diagnostics?: Diagnostics
1280
+ ): boolean {
1281
+ const resolved = targetShape.resolve(true)
1282
+ if (!resolved?.isRecord()) {
1283
+ return false
1284
+ }
1285
+ const targetRecord = resolved.asRecord()
1286
+
1287
+ // Validate that parentRecord ID matches target record ID
1288
+ if (parentRecord.getId().getValue() === targetRecord.getId().getValue()) {
1289
+ return true
1290
+ }
1291
+
1292
+ if (appliesTo === 'item') {
1293
+ // Validate that variable is from variable set AND that variable set is added to the catalog item
1294
+ const isValid = targetRecord
1295
+ .flat()
1296
+ .some(
1297
+ (r: Record) =>
1298
+ r.getTable() === 'io_set_item' &&
1299
+ r.get('sc_cat_item')?.asRecord()?.getId()?.getValue() === targetRecord.getId()?.getValue()
1300
+ )
1301
+ if (!isValid && diagnostics) {
1302
+ diagnostics.error(targetShape.getOriginalNode(), 'Variable is not in the catalog item')
1303
+ }
1304
+ return isValid
1305
+ }
1306
+
1307
+ // For 'set' case
1308
+ if (diagnostics) {
1309
+ diagnostics.error(targetShape.getOriginalNode(), 'Variable is not in the variable set')
1310
+ }
1311
+ return false
1312
+ }
1313
+
1314
+ /**
1315
+ * Resolves a variable ID from variableNameShape and validates it belongs to the variable set if appliesTo is 'set'
1316
+ * @param variableNameShape - The shape from get('variableName', false)
1317
+ * @param appliesTo - The value from get('appliesTo')
1318
+ * @param variableSetShape - The shape from get('variableSet', false)
1319
+ * @param diagnostics - Diagnostics instance for reporting errors
1320
+ * @returns The resolved variable ID with IO: prefix, or undefined if resolution fails
1321
+ */
1322
+ export function resolveAndValidateVariableId(
1323
+ variableNameShape: Shape<unknown> | undefined,
1324
+ appliesTo: 'set' | 'item',
1325
+ variableSetShape: Shape<unknown> | undefined,
1326
+ catalogItemShape: Shape<unknown> | undefined,
1327
+ diagnostics: Diagnostics
1328
+ ): string | undefined {
1329
+ if (!variableNameShape) {
1330
+ return undefined
1331
+ }
1332
+
1333
+ let variableId: string | undefined
1334
+
1335
+ if (variableNameShape.is(PropertyAccessShape)) {
1336
+ const variableAccess = resolveVariableAccess(variableNameShape.as(PropertyAccessShape))
1337
+ if (variableAccess) {
1338
+ const { parentRecord, variableName } = variableAccess
1339
+ variableId = resolveVariableId(parentRecord, variableName)
1340
+
1341
+ // Validate variable belongs to target based on appliesTo (default to 'item')
1342
+ const appliesToValue: 'set' | 'item' = appliesTo === 'set' ? 'set' : 'item'
1343
+ const targetShape = appliesToValue === 'set' ? variableSetShape : catalogItemShape
1344
+ if (targetShape?.is(IdentifierShape)) {
1345
+ validateVariableBelongsToTarget(
1346
+ targetShape.as(IdentifierShape),
1347
+ parentRecord,
1348
+ appliesToValue,
1349
+ diagnostics
1350
+ )
1351
+ }
1352
+ } else {
1353
+ // Fallback to string value if resolution fails
1354
+ variableId = `IO:${variableNameShape.toString()?.getValue()}`
1355
+ }
1356
+ } else {
1357
+ // Handle non-PropertyAccessShape case
1358
+ variableId = `IO:${variableNameShape.toString()?.getValue()}`
1359
+ }
1360
+
1361
+ return variableId
1362
+ }
1363
+
1364
+ /**
1365
+ * Validates that each variable's 'field' value (when 'mapToField' is true) belongs to the record producer's target table.
1366
+ * Resolves the table record from arg.get('table'), collects field names from sys_documentation descendants via .flat(),
1367
+ * and checks each variable's 'field' against those keys.
1368
+ * @param arg - The ObjectShape containing the record producer configuration
1369
+ * @param diagnostics - Diagnostics instance for reporting errors
1370
+ * @param context - Context where the validation is being performed ('RecordProducer')
1371
+ * @returns True if all mapped fields belong to the table, false otherwise
1372
+ */
1373
+ export function validateFieldNameBelongsToTable(arg: ObjectShape, diagnostics: Diagnostics, context: string): boolean {
1374
+ if (!arg.get('variables').isDefined()) {
1375
+ return true
1376
+ }
1377
+
1378
+ // Resolve the table record from arg.get('table')
1379
+ // table can be a direct record reference (IdentifierShape) or a plain string table name
1380
+ const tableShape = arg.get('table')
1381
+
1382
+ let tableRecord: Record | undefined
1383
+
1384
+ if (tableShape.isRecord()) {
1385
+ tableRecord = tableShape.asRecord()
1386
+ } else if (tableShape.is(IdentifierShape)) {
1387
+ const resolved = tableShape.as(IdentifierShape).resolve(true)
1388
+ if (resolved?.isRecord()) {
1389
+ tableRecord = resolved.asRecord()
1390
+ }
1391
+ }
1392
+
1393
+ // Collect field names from sys_documentation descendants via .flat() (only when table resolves to a record)
1394
+ let tableFieldSet: Set<string> | undefined
1395
+
1396
+ if (tableRecord) {
1397
+ const tableFields = tableRecord
1398
+ .flat()
1399
+ .filter((r: Record) => r.getTable() === 'sys_dictionary')
1400
+ .map((r: Record) => r.get('element')?.ifString()?.getValue())
1401
+ .filter((element): element is string => !!element)
1402
+
1403
+ if (tableFields.length > 0) {
1404
+ tableFieldSet = new Set(tableFields)
1405
+ }
1406
+ }
1407
+
1408
+ // Iterate over variables and validate field names for those with mapToField: true
1409
+ const variablesConfig = arg.get('variables').asObject()
1410
+ const entries = Array.from(variablesConfig.entries())
1411
+
1412
+ // Track which fields have already been mapped to detect duplicates (always enforced)
1413
+ const mappedFields = new Map<string, string>() // field -> first variableKey that mapped it
1414
+
1415
+ for (const [variableKey, value] of entries) {
1416
+ const callExpr = value.as(CallExpressionShape)
1417
+ const config = callExpr.getArgument(0).asObject()
1418
+
1419
+ const mapToField = config.get('mapToField')
1420
+ const isMapToFieldTrue = mapToField.isDefined() && mapToField.ifBoolean()?.getValue() === true
1421
+
1422
+ if (!isMapToFieldTrue) {
1423
+ continue
1424
+ }
1425
+
1426
+ const fieldShape = config.get('field')
1427
+ const fieldValue = fieldShape.isDefined() ? fieldShape.ifString()?.getValue() : undefined
1428
+
1429
+ if (!fieldValue) {
1430
+ continue
1431
+ }
1432
+
1433
+ // Check field belongs to table (only when table resolved to a record with sys_documentation)
1434
+ if (tableFieldSet && !tableFieldSet.has(fieldValue)) {
1435
+ diagnostics.error(
1436
+ fieldShape,
1437
+ `${context} variable '${variableKey}': field '${fieldValue}' does not belong to the target table. Valid fields are: ${[...tableFieldSet].join(', ')}.`
1438
+ )
1439
+ return false
1440
+ }
1441
+
1442
+ // Check if this field is already mapped by another variable (always enforced)
1443
+ const existingVariable = mappedFields.get(fieldValue)
1444
+ if (existingVariable) {
1445
+ diagnostics.error(
1446
+ fieldShape,
1447
+ `${context} variable '${variableKey}': field '${fieldValue}' is already mapped by variable '${existingVariable}'. Each table field can only be mapped by one variable.`
1448
+ )
1449
+ return false
1450
+ }
1451
+ mappedFields.set(fieldValue, variableKey)
1452
+ }
1453
+
1454
+ return true
1455
+ }