@servicenow/sdk-build-plugins 4.4.1 → 4.6.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 (337) hide show
  1. package/dist/acl-plugin.js +54 -4
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.js +2 -0
  4. package/dist/applicability-plugin.js.map +1 -1
  5. package/dist/application-menu-plugin.js +2 -0
  6. package/dist/application-menu-plugin.js.map +1 -1
  7. package/dist/arrow-function-plugin.d.ts +6 -1
  8. package/dist/arrow-function-plugin.js +105 -12
  9. package/dist/arrow-function-plugin.js.map +1 -1
  10. package/dist/atf/test-plugin.js +2 -0
  11. package/dist/atf/test-plugin.js.map +1 -1
  12. package/dist/basic-syntax-plugin.js +20 -0
  13. package/dist/basic-syntax-plugin.js.map +1 -1
  14. package/dist/call-expression-plugin.js +1 -0
  15. package/dist/call-expression-plugin.js.map +1 -1
  16. package/dist/claims-plugin.js +1 -0
  17. package/dist/claims-plugin.js.map +1 -1
  18. package/dist/client-script-plugin.js +1 -0
  19. package/dist/client-script-plugin.js.map +1 -1
  20. package/dist/column-plugin.js +4 -7
  21. package/dist/column-plugin.js.map +1 -1
  22. package/dist/cross-scope-privilege-plugin.js +1 -0
  23. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  24. package/dist/dashboard/dashboard-plugin.js +2 -0
  25. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  26. package/dist/data-plugin.js +1 -0
  27. package/dist/data-plugin.js.map +1 -1
  28. package/dist/email-notification-plugin.js +9 -13
  29. package/dist/email-notification-plugin.js.map +1 -1
  30. package/dist/flow/constants/flow-plugin-constants.d.ts +1 -1
  31. package/dist/flow/constants/flow-plugin-constants.js +1 -1
  32. package/dist/flow/constants/flow-plugin-constants.js.map +1 -1
  33. package/dist/flow/flow-logic/flow-logic-diagnostics.js +5 -5
  34. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  35. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +82 -2
  36. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +48 -40
  37. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -1
  38. package/dist/flow/flow-logic/flow-logic-plugin.js +1 -0
  39. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  40. package/dist/flow/plugins/approval-rules-plugin.js +1 -0
  41. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -1
  42. package/dist/flow/plugins/flow-action-definition-plugin.js +1232 -55
  43. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  44. package/dist/flow/plugins/flow-data-pill-plugin.js +6 -2
  45. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  46. package/dist/flow/plugins/flow-definition-plugin.js +23 -44
  47. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  48. package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +2 -2
  49. package/dist/flow/plugins/flow-diagnostics-plugin.js +3 -2
  50. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -1
  51. package/dist/flow/plugins/flow-instance-plugin.js +136 -34
  52. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  53. package/dist/flow/plugins/flow-trigger-instance-plugin.js +1 -0
  54. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
  55. package/dist/flow/plugins/inline-script-plugin.js +1 -0
  56. package/dist/flow/plugins/inline-script-plugin.js.map +1 -1
  57. package/dist/flow/plugins/step-definition-plugin.js +4 -2
  58. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  59. package/dist/flow/plugins/step-instance-plugin.d.ts +9 -1
  60. package/dist/flow/plugins/step-instance-plugin.js +649 -135
  61. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  62. package/dist/flow/plugins/trigger-plugin.js +2 -0
  63. package/dist/flow/plugins/trigger-plugin.js.map +1 -1
  64. package/dist/flow/plugins/wfa-datapill-plugin.js +21 -5
  65. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  66. package/dist/flow/post-install.d.ts +2 -0
  67. package/dist/flow/post-install.js +59 -0
  68. package/dist/flow/post-install.js.map +1 -0
  69. package/dist/flow/utils/complex-object-resolver.js +4 -1
  70. package/dist/flow/utils/complex-object-resolver.js.map +1 -1
  71. package/dist/flow/utils/complex-objects.js +5 -3
  72. package/dist/flow/utils/complex-objects.js.map +1 -1
  73. package/dist/flow/utils/flow-constants.d.ts +90 -2
  74. package/dist/flow/utils/flow-constants.js +430 -7
  75. package/dist/flow/utils/flow-constants.js.map +1 -1
  76. package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
  77. package/dist/flow/utils/flow-io-to-record.js +37 -16
  78. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  79. package/dist/flow/utils/flow-shapes.js +4 -0
  80. package/dist/flow/utils/flow-shapes.js.map +1 -1
  81. package/dist/flow/utils/flow-to-xml.d.ts +3 -2
  82. package/dist/flow/utils/flow-to-xml.js +3 -4
  83. package/dist/flow/utils/flow-to-xml.js.map +1 -1
  84. package/dist/flow/utils/label-cache-parser.d.ts +9 -2
  85. package/dist/flow/utils/label-cache-parser.js +32 -4
  86. package/dist/flow/utils/label-cache-parser.js.map +1 -1
  87. package/dist/flow/utils/label-cache-processor.d.ts +5 -0
  88. package/dist/flow/utils/label-cache-processor.js +14 -2
  89. package/dist/flow/utils/label-cache-processor.js.map +1 -1
  90. package/dist/flow/utils/pill-shape-helpers.d.ts +15 -0
  91. package/dist/flow/utils/pill-shape-helpers.js +35 -0
  92. package/dist/flow/utils/pill-shape-helpers.js.map +1 -0
  93. package/dist/flow/utils/pill-string-parser.js +1 -0
  94. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  95. package/dist/flow/utils/schema-to-flow-object.d.ts +6 -1
  96. package/dist/flow/utils/schema-to-flow-object.js +131 -15
  97. package/dist/flow/utils/schema-to-flow-object.js.map +1 -1
  98. package/dist/flow/utils/service-catalog.js +5 -1
  99. package/dist/flow/utils/service-catalog.js.map +1 -1
  100. package/dist/flow/utils/utils.d.ts +1 -0
  101. package/dist/flow/utils/utils.js +6 -1
  102. package/dist/flow/utils/utils.js.map +1 -1
  103. package/dist/form-plugin.d.ts +2 -0
  104. package/dist/form-plugin.js +1132 -0
  105. package/dist/form-plugin.js.map +1 -0
  106. package/dist/html-import-plugin.js +1 -0
  107. package/dist/html-import-plugin.js.map +1 -1
  108. package/dist/import-sets-plugin.js +2 -0
  109. package/dist/import-sets-plugin.js.map +1 -1
  110. package/dist/inbound-email-action-plugin.d.ts +10 -0
  111. package/dist/inbound-email-action-plugin.js +128 -0
  112. package/dist/inbound-email-action-plugin.js.map +1 -0
  113. package/dist/index.d.ts +13 -0
  114. package/dist/index.js +17 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/instance-scan-plugin.d.ts +2 -0
  117. package/dist/instance-scan-plugin.js +293 -0
  118. package/dist/instance-scan-plugin.js.map +1 -0
  119. package/dist/json-plugin.js +1 -0
  120. package/dist/json-plugin.js.map +1 -1
  121. package/dist/list-plugin.js +1 -0
  122. package/dist/list-plugin.js.map +1 -1
  123. package/dist/now-attach-plugin.js +1 -0
  124. package/dist/now-attach-plugin.js.map +1 -1
  125. package/dist/now-config-plugin.js +659 -51
  126. package/dist/now-config-plugin.js.map +1 -1
  127. package/dist/now-id-plugin.js +1 -0
  128. package/dist/now-id-plugin.js.map +1 -1
  129. package/dist/now-include-plugin.js +1 -0
  130. package/dist/now-include-plugin.js.map +1 -1
  131. package/dist/now-ref-plugin.js +1 -0
  132. package/dist/now-ref-plugin.js.map +1 -1
  133. package/dist/now-unresolved-plugin.js +1 -0
  134. package/dist/now-unresolved-plugin.js.map +1 -1
  135. package/dist/package-json-plugin.js +1 -0
  136. package/dist/package-json-plugin.js.map +1 -1
  137. package/dist/property-plugin.js +3 -1
  138. package/dist/property-plugin.js.map +1 -1
  139. package/dist/record-plugin.d.ts +37 -0
  140. package/dist/record-plugin.js +47 -3
  141. package/dist/record-plugin.js.map +1 -1
  142. package/dist/repack/lint/Rules.d.ts +11 -2
  143. package/dist/repack/lint/Rules.js +160 -16
  144. package/dist/repack/lint/Rules.js.map +1 -1
  145. package/dist/repack/lint/index.d.ts +10 -5
  146. package/dist/repack/lint/index.js +76 -50
  147. package/dist/repack/lint/index.js.map +1 -1
  148. package/dist/rest-api-plugin.js +22 -1
  149. package/dist/rest-api-plugin.js.map +1 -1
  150. package/dist/role-plugin.js +1 -0
  151. package/dist/role-plugin.js.map +1 -1
  152. package/dist/schedule-script/index.d.ts +1 -0
  153. package/dist/schedule-script/index.js +18 -0
  154. package/dist/schedule-script/index.js.map +1 -0
  155. package/dist/schedule-script/scheduled-script-plugin.d.ts +2 -0
  156. package/dist/schedule-script/scheduled-script-plugin.js +556 -0
  157. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -0
  158. package/dist/schedule-script/timeZoneConverter.d.ts +61 -0
  159. package/dist/schedule-script/timeZoneConverter.js +170 -0
  160. package/dist/schedule-script/timeZoneConverter.js.map +1 -0
  161. package/dist/script-action-plugin.js +2 -0
  162. package/dist/script-action-plugin.js.map +1 -1
  163. package/dist/script-include-plugin.js +2 -0
  164. package/dist/script-include-plugin.js.map +1 -1
  165. package/dist/server-module-plugin/index.js +13 -2
  166. package/dist/server-module-plugin/index.js.map +1 -1
  167. package/dist/service-catalog/catalog-clientscript-plugin.js +2 -0
  168. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  169. package/dist/service-catalog/catalog-item-plugin.js +2 -0
  170. package/dist/service-catalog/catalog-item-plugin.js.map +1 -1
  171. package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -0
  172. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  173. package/dist/service-catalog/sc-record-producer-plugin.js +2 -0
  174. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
  175. package/dist/service-catalog/service-catalog-base.d.ts +18 -18
  176. package/dist/service-catalog/service-catalog-base.js +22 -22
  177. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  178. package/dist/service-catalog/service-catalog-diagnostics.d.ts +6 -0
  179. package/dist/service-catalog/service-catalog-diagnostics.js +20 -0
  180. package/dist/service-catalog/service-catalog-diagnostics.js.map +1 -1
  181. package/dist/service-catalog/shape-to-record.js +7 -2
  182. package/dist/service-catalog/shape-to-record.js.map +1 -1
  183. package/dist/service-catalog/variable-set-plugin.js +2 -0
  184. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  185. package/dist/service-portal/angular-provider-plugin.js +2 -0
  186. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  187. package/dist/service-portal/dependency-plugin.js +5 -31
  188. package/dist/service-portal/dependency-plugin.js.map +1 -1
  189. package/dist/service-portal/header-footer-plugin.d.ts +2 -0
  190. package/dist/service-portal/header-footer-plugin.js +50 -0
  191. package/dist/service-portal/header-footer-plugin.js.map +1 -0
  192. package/dist/service-portal/menu-plugin.d.ts +2 -0
  193. package/dist/service-portal/menu-plugin.js +334 -0
  194. package/dist/service-portal/menu-plugin.js.map +1 -0
  195. package/dist/service-portal/page-plugin.d.ts +2 -0
  196. package/dist/service-portal/page-plugin.js +681 -0
  197. package/dist/service-portal/page-plugin.js.map +1 -0
  198. package/dist/service-portal/page-route-map-plugin.d.ts +2 -0
  199. package/dist/service-portal/page-route-map-plugin.js +114 -0
  200. package/dist/service-portal/page-route-map-plugin.js.map +1 -0
  201. package/dist/service-portal/portal-plugin.d.ts +2 -0
  202. package/dist/service-portal/portal-plugin.js +309 -0
  203. package/dist/service-portal/portal-plugin.js.map +1 -0
  204. package/dist/service-portal/theme-plugin.d.ts +2 -0
  205. package/dist/service-portal/theme-plugin.js +112 -0
  206. package/dist/service-portal/theme-plugin.js.map +1 -0
  207. package/dist/service-portal/utils.d.ts +46 -0
  208. package/dist/service-portal/utils.js +331 -0
  209. package/dist/service-portal/utils.js.map +1 -0
  210. package/dist/service-portal/widget-plugin.js +10 -182
  211. package/dist/service-portal/widget-plugin.js.map +1 -1
  212. package/dist/sla-plugin.js +2 -0
  213. package/dist/sla-plugin.js.map +1 -1
  214. package/dist/static-content-plugin.js +5 -0
  215. package/dist/static-content-plugin.js.map +1 -1
  216. package/dist/table-plugin.js +191 -26
  217. package/dist/table-plugin.js.map +1 -1
  218. package/dist/ui-action-plugin.js +3 -4
  219. package/dist/ui-action-plugin.js.map +1 -1
  220. package/dist/ui-page-plugin.js +101 -21
  221. package/dist/ui-page-plugin.js.map +1 -1
  222. package/dist/ui-policy-plugin.js +1 -0
  223. package/dist/ui-policy-plugin.js.map +1 -1
  224. package/dist/user-preference-plugin.js +2 -0
  225. package/dist/user-preference-plugin.js.map +1 -1
  226. package/dist/utils.d.ts +20 -2
  227. package/dist/utils.js +34 -3
  228. package/dist/utils.js.map +1 -1
  229. package/dist/ux-list-menu-config-plugin.js +2 -0
  230. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  231. package/dist/view-plugin.js +9 -3
  232. package/dist/view-plugin.js.map +1 -1
  233. package/dist/workspace-plugin.js +41 -36
  234. package/dist/workspace-plugin.js.map +1 -1
  235. package/package.json +11 -11
  236. package/src/_types/eslint-community-eslint-utils.d.ts +15 -0
  237. package/src/acl-plugin.ts +97 -8
  238. package/src/applicability-plugin.ts +2 -0
  239. package/src/application-menu-plugin.ts +2 -0
  240. package/src/arrow-function-plugin.ts +128 -13
  241. package/src/atf/test-plugin.ts +2 -0
  242. package/src/basic-syntax-plugin.ts +21 -0
  243. package/src/call-expression-plugin.ts +1 -0
  244. package/src/claims-plugin.ts +1 -0
  245. package/src/client-script-plugin.ts +2 -1
  246. package/src/column-plugin.ts +4 -8
  247. package/src/cross-scope-privilege-plugin.ts +2 -1
  248. package/src/dashboard/dashboard-plugin.ts +2 -0
  249. package/src/data-plugin.ts +1 -0
  250. package/src/email-notification-plugin.ts +3 -23
  251. package/src/flow/constants/flow-plugin-constants.ts +1 -1
  252. package/src/flow/flow-logic/flow-logic-diagnostics.ts +5 -6
  253. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +47 -45
  254. package/src/flow/flow-logic/flow-logic-plugin.ts +1 -0
  255. package/src/flow/plugins/approval-rules-plugin.ts +1 -0
  256. package/src/flow/plugins/flow-action-definition-plugin.ts +1584 -62
  257. package/src/flow/plugins/flow-data-pill-plugin.ts +6 -2
  258. package/src/flow/plugins/flow-definition-plugin.ts +22 -51
  259. package/src/flow/plugins/flow-diagnostics-plugin.ts +3 -2
  260. package/src/flow/plugins/flow-instance-plugin.ts +201 -36
  261. package/src/flow/plugins/flow-trigger-instance-plugin.ts +1 -0
  262. package/src/flow/plugins/inline-script-plugin.ts +1 -0
  263. package/src/flow/plugins/step-definition-plugin.ts +4 -2
  264. package/src/flow/plugins/step-instance-plugin.ts +772 -155
  265. package/src/flow/plugins/trigger-plugin.ts +2 -0
  266. package/src/flow/plugins/wfa-datapill-plugin.ts +26 -5
  267. package/src/flow/post-install.ts +93 -0
  268. package/src/flow/utils/complex-object-resolver.ts +4 -1
  269. package/src/flow/utils/complex-objects.ts +11 -3
  270. package/src/flow/utils/flow-constants.ts +451 -6
  271. package/src/flow/utils/flow-io-to-record.ts +43 -17
  272. package/src/flow/utils/flow-shapes.ts +4 -0
  273. package/src/flow/utils/flow-to-xml.ts +4 -4
  274. package/src/flow/utils/label-cache-parser.ts +33 -4
  275. package/src/flow/utils/label-cache-processor.ts +14 -2
  276. package/src/flow/utils/pill-shape-helpers.ts +42 -0
  277. package/src/flow/utils/pill-string-parser.ts +1 -0
  278. package/src/flow/utils/schema-to-flow-object.ts +183 -15
  279. package/src/flow/utils/service-catalog.ts +5 -2
  280. package/src/flow/utils/utils.ts +12 -1
  281. package/src/form-plugin.ts +1409 -0
  282. package/src/html-import-plugin.ts +1 -0
  283. package/src/import-sets-plugin.ts +2 -0
  284. package/src/inbound-email-action-plugin.ts +145 -0
  285. package/src/index.ts +13 -0
  286. package/src/instance-scan-plugin.ts +313 -0
  287. package/src/json-plugin.ts +1 -0
  288. package/src/list-plugin.ts +2 -1
  289. package/src/now-attach-plugin.ts +1 -0
  290. package/src/now-config-plugin.ts +833 -53
  291. package/src/now-id-plugin.ts +1 -0
  292. package/src/now-include-plugin.ts +1 -0
  293. package/src/now-ref-plugin.ts +1 -0
  294. package/src/now-unresolved-plugin.ts +1 -0
  295. package/src/package-json-plugin.ts +1 -0
  296. package/src/property-plugin.ts +6 -1
  297. package/src/record-plugin.ts +56 -6
  298. package/src/repack/lint/Rules.ts +171 -22
  299. package/src/repack/lint/index.ts +80 -56
  300. package/src/rest-api-plugin.ts +28 -2
  301. package/src/role-plugin.ts +2 -1
  302. package/src/schedule-script/index.ts +1 -0
  303. package/src/schedule-script/scheduled-script-plugin.ts +690 -0
  304. package/src/schedule-script/timeZoneConverter.ts +188 -0
  305. package/src/script-action-plugin.ts +2 -0
  306. package/src/script-include-plugin.ts +2 -0
  307. package/src/server-module-plugin/index.ts +14 -2
  308. package/src/service-catalog/catalog-clientscript-plugin.ts +2 -0
  309. package/src/service-catalog/catalog-item-plugin.ts +2 -0
  310. package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -0
  311. package/src/service-catalog/sc-record-producer-plugin.ts +2 -0
  312. package/src/service-catalog/service-catalog-base.ts +22 -22
  313. package/src/service-catalog/service-catalog-diagnostics.ts +30 -0
  314. package/src/service-catalog/shape-to-record.ts +8 -2
  315. package/src/service-catalog/variable-set-plugin.ts +2 -0
  316. package/src/service-portal/angular-provider-plugin.ts +2 -0
  317. package/src/service-portal/dependency-plugin.ts +6 -53
  318. package/src/service-portal/header-footer-plugin.ts +57 -0
  319. package/src/service-portal/menu-plugin.ts +413 -0
  320. package/src/service-portal/page-plugin.ts +805 -0
  321. package/src/service-portal/page-route-map-plugin.ts +124 -0
  322. package/src/service-portal/portal-plugin.ts +342 -0
  323. package/src/service-portal/theme-plugin.ts +135 -0
  324. package/src/service-portal/utils.ts +470 -0
  325. package/src/service-portal/widget-plugin.ts +18 -224
  326. package/src/sla-plugin.ts +2 -0
  327. package/src/static-content-plugin.ts +4 -0
  328. package/src/table-plugin.ts +228 -37
  329. package/src/ui-action-plugin.ts +3 -8
  330. package/src/ui-page-plugin.ts +110 -21
  331. package/src/ui-policy-plugin.ts +2 -1
  332. package/src/user-preference-plugin.ts +2 -0
  333. package/src/utils.ts +42 -2
  334. package/src/ux-list-menu-config-plugin.ts +2 -0
  335. package/src/view-plugin.ts +11 -4
  336. package/src/workspace-plugin.ts +45 -43
  337. package/src/_types/eslint-plugin-es-x.d.ts +0 -17
@@ -0,0 +1,805 @@
1
+ import {
2
+ CallExpressionShape,
3
+ Plugin,
4
+ type Factory,
5
+ //type Diagnostics,
6
+ type Record,
7
+ type RecordId,
8
+ type Shape,
9
+ type ObjectShape,
10
+ } from '@servicenow/sdk-build-core'
11
+ import isEmpty from 'lodash/isEmpty'
12
+ import { toReference, getFieldAsNumber, createSdkDocEntry } from '../utils'
13
+ import { getRolesString } from './utils'
14
+
15
+ type Dict = { [key: string]: unknown }
16
+
17
+ const default_placeholder_dimensions = `{
18
+ "mobile": {
19
+ "height": "250px",
20
+ "width": "100%"
21
+ },
22
+ "desktop": {
23
+ "height": "250px",
24
+ "width": "100%"
25
+ },
26
+ "tablet": {
27
+ "height": "250px",
28
+ "width": "100%"
29
+ }
30
+ }`
31
+
32
+ const default_placeholder_template = `<!--
33
+ AngularJS template with configurable options.
34
+ Use the "options" object to control dynamic behavior.
35
+ Example: Display an element when max row count is 10:
36
+ <div ng-if="options.maxRowCount === 10"></div>
37
+ The "skeleton-container" class is used for loading placeholders.
38
+ -->
39
+ <div class="skeleton-container">
40
+ <!-- Header Skeleton -->
41
+ <div class="skeleton-box skeleton-header"></div>
42
+ <!-- Body Skeleton -->
43
+ <div class="skeleton-box skeleton-line"></div>
44
+ <div class="skeleton-box skeleton-line small"></div>
45
+ <div class="skeleton-box skeleton-line medium"></div>
46
+ </div>`
47
+
48
+ const default_placeholder_script = `function evaluateConfig(options) { return {
49
+ "mobile": {
50
+ "height": "250px",
51
+ "width": "100%"
52
+ },
53
+ "desktop": {
54
+ "height": "250px",
55
+ "width": "100%"
56
+ },
57
+ "tablet": {
58
+ "height": "250px",
59
+ "width": "100%"
60
+ }
61
+ }; }`
62
+
63
+ const defaultValues = {
64
+ container: {
65
+ width: 'container',
66
+ backgroundStyle: 'default',
67
+ subheader: false,
68
+ bootstrapAlt: false,
69
+ },
70
+ instance: {
71
+ active: true,
72
+ color: 'default',
73
+ size: 'md',
74
+ asyncLoadDeviceType: 'desktop,tablet,mobile',
75
+ },
76
+ column: {
77
+ size: 12,
78
+ },
79
+ page: {
80
+ category: 'custom',
81
+ useSeoScript: false,
82
+ shortDescription: '',
83
+ public: false,
84
+ draft: false,
85
+ omitWatcher: false,
86
+ internal: false,
87
+ },
88
+ }
89
+
90
+ /**
91
+ * Safely parse a size value from a Shape, handling non-numeric strings
92
+ * @param shape - The shape to extract the size from
93
+ * @param fieldName - The field name to extract (e.g., "size", "size_sm")
94
+ * @param defaultValue - Default value if parsing fails
95
+ * @returns Numeric size value or undefined
96
+ */
97
+
98
+ /**
99
+ * Sort an array of shapes by their order field
100
+ * @param shapes - Array of shapes to sort
101
+ * @returns Sorted array
102
+ */
103
+ function sortByOrder<T extends Record>(shapes: T[]): T[] {
104
+ return shapes.sort((a, b) => {
105
+ const aOrder = getFieldAsNumber(a, 'order', 1)
106
+ const bOrder = getFieldAsNumber(b, 'order', 1)
107
+ return (aOrder ?? 1) - (bOrder ?? 1)
108
+ })
109
+ }
110
+
111
+ /**
112
+ * Conditionally adds a property to an object if the value is not empty or undefined.
113
+ * Filters out undefined values, empty arrays, and empty strings to keep objects clean.
114
+ * @param obj - The target object to add the property to
115
+ * @param key - The property key to add
116
+ * @param value - The value to add (will be filtered if empty/undefined)
117
+ */
118
+
119
+ const addProperty = (obj: Dict, key: string, value: unknown, objectType?: keyof typeof defaultValues) => {
120
+ if (
121
+ value === undefined ||
122
+ (Array.isArray(value) && value.length === 0) ||
123
+ (typeof value === 'string' && value === '')
124
+ ) {
125
+ return
126
+ }
127
+
128
+ // Check if value matches default for this object type
129
+ if (objectType && defaultValues[objectType] && (defaultValues[objectType] as Dict)[key] === value) {
130
+ return
131
+ }
132
+ obj[key] = value
133
+ }
134
+
135
+ /**
136
+ * Creates a container object from a ServiceNow sp_container record Shape.
137
+ * Transforms database fields to Fluent API format and includes nested rows.
138
+ * @param container - The sp_container record Shape
139
+ * @param rows - Array of row objects that belong to this container
140
+ * @returns Formatted container object or undefined if container is empty
141
+ */
142
+ const getContainerObject = (container: Record, rows: object[]): Dict | undefined => {
143
+ if (!container || isEmpty(container)) {
144
+ return
145
+ }
146
+
147
+ const name = container.get('name').ifString()?.getValue()
148
+ const width = container.get('width').ifString()?.getValue()
149
+ const backgroundStyle = container.get('background_style').ifString()?.getValue()
150
+ const backgroundColor = container.get('background_color').ifString()?.getValue()
151
+ const backgroundImage = container.get('background_image').ifString()?.getValue()
152
+ const cssClass = container.get('class_name').ifString()?.getValue()
153
+ const parentClass = container.get('container_class_name').ifString()?.getValue()
154
+ const subheader = container.get('subheader').toBoolean()?.getValue()
155
+ const bootstrapAlt = container.get('bootstrap_alt').toBoolean()?.getValue()
156
+ const semanticTag = container.get('semantic_tag').ifString()?.getValue()
157
+ const title = container.get('title').ifString()?.getValue()
158
+
159
+ const containerObject: Dict = {}
160
+
161
+ addProperty(containerObject, '$id', container.getId())
162
+ addProperty(containerObject, 'order', getFieldAsNumber(container, 'order', 1))
163
+ addProperty(containerObject, 'name', name)
164
+ addProperty(containerObject, 'width', width, 'container')
165
+ addProperty(containerObject, 'backgroundStyle', backgroundStyle, 'container')
166
+ addProperty(containerObject, 'backgroundColor', backgroundColor, 'container')
167
+ addProperty(containerObject, 'backgroundImage', backgroundImage)
168
+ addProperty(containerObject, 'cssClass', cssClass)
169
+ addProperty(containerObject, 'parentClass', parentClass)
170
+ addProperty(containerObject, 'subheader', subheader, 'container')
171
+ addProperty(containerObject, 'bootstrapAlt', bootstrapAlt, 'container')
172
+ addProperty(containerObject, 'semanticTag', semanticTag)
173
+ addProperty(containerObject, 'title', title)
174
+ addProperty(containerObject, 'rows', rows)
175
+
176
+ return containerObject
177
+ }
178
+
179
+ /**
180
+ * Creates a row object from a ServiceNow sp_row record Shape.
181
+ * Transforms database fields to Fluent API format and includes nested columns.
182
+ * @param row - The sp_row record Shape
183
+ * @param columns - Array of column objects that belong to this row
184
+ * @returns Formatted row object or undefined if row is empty
185
+ */
186
+ const getRowObject = (row: Record, columns: object[]): Dict | undefined => {
187
+ if (!row || isEmpty(row)) {
188
+ return
189
+ }
190
+ const cssClass = row.get('class_name').ifString()?.getValue()
191
+ const semanticTag = row.get('semantic_tag').ifString()?.getValue()
192
+
193
+ const rowObject: Dict = {}
194
+ addProperty(rowObject, '$id', row.getId())
195
+ addProperty(rowObject, 'cssClass', cssClass)
196
+ addProperty(rowObject, 'semanticTag', semanticTag)
197
+ addProperty(rowObject, 'order', getFieldAsNumber(row, 'order', 1))
198
+ addProperty(rowObject, 'columns', columns)
199
+ return rowObject
200
+ }
201
+
202
+ /**
203
+ * Creates a column object from a ServiceNow sp_column record Shape.
204
+ * @param column - The sp_column record Shape
205
+ * @param instances - Array of instance objects that belong to this column
206
+ * @param nestedRows - Array of nested row objects within this column
207
+ * @returns Formatted column object or undefined if column is empty
208
+ */
209
+ const getColumnObject = (column: Record, instances: object[], nestedRows: object[]): Dict | undefined => {
210
+ if (!column || isEmpty(column)) {
211
+ return
212
+ }
213
+
214
+ const cssClass = column.get('class_name').ifString()?.getValue()
215
+ const semanticTag = column.get('semantic_tag').ifString()?.getValue()
216
+
217
+ const columnObject: Dict = {}
218
+
219
+ addProperty(columnObject, '$id', column.getId())
220
+ addProperty(columnObject, 'size', getFieldAsNumber(column, 'size', 12), 'column')
221
+ addProperty(columnObject, 'sizeSm', getFieldAsNumber(column, 'size_sm'))
222
+ addProperty(columnObject, 'sizeLg', getFieldAsNumber(column, 'size_lg'))
223
+ addProperty(columnObject, 'sizeXs', getFieldAsNumber(column, 'size_xs'))
224
+ addProperty(columnObject, 'cssClass', cssClass)
225
+ addProperty(columnObject, 'semanticTag', semanticTag)
226
+ addProperty(columnObject, 'order', getFieldAsNumber(column, 'order', 1))
227
+ addProperty(columnObject, 'instances', instances)
228
+ addProperty(columnObject, 'nestedRows', nestedRows)
229
+
230
+ return columnObject
231
+ }
232
+ /**
233
+ * Extracts and parses roles from a ServiceNow instance record.
234
+ * Converts comma-separated role string to an array of role names.
235
+ * @param instance - The sp_instance record Shape
236
+ * @returns Array of role names or undefined if no roles exist
237
+ */
238
+ const getRolesArray = (instance: Record): string[] | undefined => {
239
+ const rolesStr = instance.get('roles').ifString()?.getValue()
240
+ if (!rolesStr || rolesStr === '') {
241
+ return
242
+ }
243
+ const rolesArray = rolesStr
244
+ .split(',')
245
+ .map((role) => role.trim())
246
+ .filter((role) => role !== '')
247
+ return rolesArray.length > 0 ? rolesArray : undefined
248
+ }
249
+
250
+ /**
251
+ * Creates an instance object from a ServiceNow sp_instance record Shape.
252
+ * Transforms all instance properties from database format to Fluent API format,
253
+ * including widget references, styling, roles, and configuration.
254
+ * @param instance - The sp_instance record Shape
255
+ * @returns Formatted instance object or undefined if instance is empty
256
+ */
257
+ function getInstanceObject(instance: Record): object | undefined {
258
+ if (!instance || isEmpty(instance)) {
259
+ return
260
+ }
261
+
262
+ const title = instance.get('title').ifString()?.getValue()
263
+ const id = instance.get('id').ifString()?.getValue()
264
+ const widget = instance.get('sp_widget').ifString()?.getValue()
265
+ const widgetParameters = instance.get('widget_parameters').ifString()?.getValue()
266
+ const css = instance.get('css').ifString()?.getValue()
267
+ const url = instance.get('url').ifString()?.getValue()
268
+ const glyph = instance.get('glyph').ifString()?.getValue()
269
+ const size = instance.get('size').ifString()?.getValue()
270
+ const color = instance.get('color').ifString()?.getValue()
271
+ const cssClass = instance.get('class_name').ifString()?.getValue()
272
+ const shortDescription = instance.get('short_description').ifString()?.getValue()
273
+ const active = instance.get('active').toBoolean()?.getValue()
274
+
275
+ const instanceObject: Dict = {}
276
+ addProperty(instanceObject, '$id', instance.getId())
277
+ addProperty(instanceObject, 'title', title)
278
+ addProperty(instanceObject, 'id', id)
279
+ addProperty(instanceObject, 'widget', widget)
280
+ addProperty(instanceObject, 'widgetParameters', widgetParameters)
281
+ addProperty(instanceObject, 'css', css)
282
+ addProperty(instanceObject, 'url', url)
283
+ addProperty(instanceObject, 'glyph', glyph)
284
+ addProperty(instanceObject, 'size', size, 'instance')
285
+ addProperty(instanceObject, 'color', color, 'instance')
286
+ addProperty(instanceObject, 'cssClass', cssClass)
287
+ addProperty(instanceObject, 'active', active, 'instance')
288
+ addProperty(instanceObject, 'order', getFieldAsNumber(instance, 'order', 1))
289
+ addProperty(instanceObject, 'roles', getRolesArray(instance))
290
+ addProperty(instanceObject, 'shortDescription', shortDescription)
291
+
292
+ return instanceObject
293
+ }
294
+
295
+ /**
296
+ * Recursively processes nested rows within a column
297
+ * @param columnRows - Rows that belong to the column
298
+ * @param allColumns - All available columns
299
+ * @param allInstances - All available instances
300
+ * @returns Array of processed SPRow objects with nested structure
301
+ */
302
+ function getNestedRows(columnRows: Record[], allRows: Record[], allColumns: Record[], allInstances: Record[]): Dict[] {
303
+ if (!columnRows || columnRows.length === 0) {
304
+ return []
305
+ }
306
+
307
+ // Sort rows by order
308
+ const sortedRows = sortByOrder(columnRows)
309
+
310
+ return sortedRows
311
+ .map((row) => {
312
+ const rowId = row.getId()
313
+
314
+ // Get columns for this nested row
315
+ const rowColumns = sortByOrder(allColumns.filter((column) => column.get('sp_row').equals(rowId)))
316
+
317
+ const columns = rowColumns
318
+ .map((column) => {
319
+ const columnId = column.getId()
320
+
321
+ // Get instances for this column
322
+ const columnInstances = sortByOrder(
323
+ allInstances.filter((instance) => instance.get('sp_column').equals(columnId))
324
+ )
325
+
326
+ // Recursively get nested rows for this column
327
+ const nestedColumnRows = allRows.filter((row) => row.get('sp_column').equals(columnId))
328
+ const nestedRows = getNestedRows(nestedColumnRows, allRows, allColumns, allInstances)
329
+ const instances = columnInstances
330
+ .map((instance) => {
331
+ return getInstanceObject(instance)
332
+ })
333
+ .filter((instanceObject): instanceObject is Dict => Boolean(instanceObject))
334
+
335
+ return getColumnObject(column, instances, nestedRows)
336
+ })
337
+ .filter((col): col is Dict => Boolean(col))
338
+
339
+ const rowObj = getRowObject(row, columns)
340
+ return rowObj as Dict
341
+ })
342
+ .filter((row): row is Dict => Boolean(row))
343
+ }
344
+
345
+ /**
346
+ * Generates a container name from the container shape or creates a default name.
347
+ * Uses the explicit name if provided, otherwise generates a name based on page title and order.
348
+ * @param $ - The container shape object
349
+ * @param pageTitle - The title of the parent page
350
+ * @param index - The zero-based index of the container in the page
351
+ * @returns The container name string
352
+ */
353
+ const getContainerName = ($: ObjectShape, pageTitle: string, index: number): string => {
354
+ let name = $.get('name').ifString()?.getValue()
355
+ if (!name) {
356
+ const order = $.get('order').ifNumber()?.getValue() || index + 1
357
+ name = `${pageTitle} - Container ${order}`
358
+ }
359
+ return name
360
+ }
361
+
362
+ /**
363
+ * Creates ServiceNow sp_container records from Fluent container shapes and their nested rows/columns/instances.
364
+ *
365
+ * @param containersArray - Array of Fluent container Shape objects to transform
366
+ * @param pageId - RecordId of the parent sp_page record that owns these containers
367
+ * @param pageTitle - Title of the parent page, used for generating default container names
368
+ * @param factory - Factory instance for creating ServiceNow records with proper relationships
369
+ * @returns Promise that resolves to an array of all created records (containers, rows, columns, instances)
370
+ */
371
+ const createContainerRecords = async (
372
+ containersArray: Shape[],
373
+ pageId: RecordId,
374
+ pageTitle: string,
375
+ factory: Factory
376
+ ): Promise<Record[]> => {
377
+ const records: Record[] = []
378
+
379
+ for (let index = 0; index < containersArray.length; index++) {
380
+ const containerShape = containersArray[index]
381
+ if (containerShape?.isObject()) {
382
+ const container = containerShape.asObject()
383
+
384
+ const name = getContainerName(container, pageTitle, index)
385
+
386
+ const containerRecord = await factory.createRecord({
387
+ source: containerShape,
388
+ table: 'sp_container',
389
+ explicitId: container.get('$id'),
390
+ properties: container.transform(({ $ }) => ({
391
+ name: $.val(name),
392
+ sp_page: $.val(pageId),
393
+ width: $.from('width').def('container'),
394
+ background_style: $.from('backgroundStyle').def('default'),
395
+ background_color: $.from('backgroundColor').def(''),
396
+ background_image: $.from('backgroundImage').def(''),
397
+ class_name: $.from('cssClass').def(''),
398
+ container_class_name: $.from('parentClass').def(''),
399
+ subheader: $.from('subheader').def(false),
400
+ bootstrap_alt: $.from('bootstrapAlt').def(false),
401
+ semantic_tag: $.from('semanticTag').def(''),
402
+ title: $.from('title').def(''),
403
+ order: $.from('order').def(index + 1),
404
+ })),
405
+ })
406
+
407
+ records.push(containerRecord)
408
+
409
+ // Handle rows
410
+ const rows = container.get('rows').ifArray()?.getElements() || []
411
+ const rowRecords = await createRowRecords(rows, containerRecord.getId(), factory, undefined)
412
+ records.push(...rowRecords)
413
+ }
414
+ }
415
+ return records
416
+ }
417
+
418
+ /**
419
+ * Creates ServiceNow sp_row records from Fluent row shapes and their nested columns/instances.
420
+ *
421
+ * @param rowsArray - Array of Fluent row Shape objects to transform
422
+ * @param containerId - RecordId of the parent sp_container record that owns these rows
423
+ * @param factory - Factory instance for creating ServiceNow records with proper relationships
424
+ * @param columnId - RecordId of the parent sp_column record (for nested rows within columns)
425
+ * @returns Promise that resolves to an array of all created records (rows, columns, instances)
426
+ */
427
+ const createRowRecords = async (
428
+ rowsArray: Shape[],
429
+ containerId: RecordId | undefined,
430
+ factory: Factory,
431
+ columnId: RecordId | undefined
432
+ ): Promise<Record[]> => {
433
+ const records: Record[] = []
434
+
435
+ for (let index = 0; index < rowsArray.length; index++) {
436
+ const rowShape = rowsArray[index]
437
+ if (rowShape?.isObject()) {
438
+ const row = rowShape.asObject()
439
+
440
+ const rowRecord = await factory.createRecord({
441
+ source: rowShape,
442
+ table: 'sp_row',
443
+ explicitId: row.get('$id'),
444
+ properties: row.transform(({ $ }) => ({
445
+ sp_container: $.val(containerId),
446
+ class_name: $.from('cssClass').def(''),
447
+ semantic_tag: $.from('semanticTag').def(''),
448
+ order: $.from('order').def(index + 1),
449
+ sp_column: $.val(columnId),
450
+ })),
451
+ })
452
+
453
+ records.push(rowRecord)
454
+
455
+ // Handle columns
456
+ const columns = row.get('columns').ifArray()?.getElements() || []
457
+ const columnRecords = await createColumnRecords(columns, rowRecord.getId(), factory)
458
+ records.push(...columnRecords)
459
+ }
460
+ }
461
+
462
+ return records
463
+ }
464
+
465
+ /**
466
+ * Creates ServiceNow sp_column records from Fluent column shapes and their nested instances/rows.
467
+ *
468
+ * @param columnsArray - Array of Fluent column Shape objects to transform
469
+ * @param rowId - RecordId of the parent sp_row record that owns these columns
470
+ * @param factory - Factory instance for creating ServiceNow records with proper relationships
471
+ * @returns Promise that resolves to an array of all created records (columns, instances, nested rows)
472
+ */
473
+ async function createColumnRecords(columnsArray: Shape[], rowId: RecordId, factory: Factory): Promise<Record[]> {
474
+ const records: Record[] = []
475
+
476
+ for (let index = 0; index < columnsArray.length; index++) {
477
+ const columnShape = columnsArray[index]
478
+ if (columnShape?.isObject()) {
479
+ const column = columnShape.asObject()
480
+
481
+ const columnRecord = await factory.createRecord({
482
+ source: columnShape,
483
+ table: 'sp_column',
484
+ explicitId: column.get('$id'),
485
+ properties: column.transform(({ $ }) => ({
486
+ sp_row: $.val(rowId),
487
+ size: $.from('size').def(12),
488
+ size_sm: $.from('sizeSm'),
489
+ size_lg: $.from('sizeLg'),
490
+ size_xs: $.from('sizeXs'),
491
+ class_name: $.from('cssClass').def(''),
492
+ semantic_tag: $.from('semanticTag').def(''),
493
+ order: $.from('order').def(index + 1),
494
+ })),
495
+ })
496
+
497
+ records.push(columnRecord)
498
+
499
+ // Handle instances
500
+ const instances = column.get('instances').ifArray()?.getElements() || []
501
+ const nestedRows = column.get('nestedRows').ifArray()?.getElements() || []
502
+ const nestedRowRecords = await createRowRecords(nestedRows, undefined, factory, columnRecord.getId())
503
+ records.push(...nestedRowRecords)
504
+ const instanceRecords = await createInstanceRecords(instances, columnRecord.getId(), factory)
505
+ records.push(...instanceRecords)
506
+ }
507
+ }
508
+ return records
509
+ }
510
+
511
+ /**
512
+ * Creates ServiceNow sp_instance records from Fluent instance shapes.
513
+ *
514
+ * @param instancesArray - Array of Fluent instance Shape objects to transform
515
+ * @param columnId - RecordId of the parent sp_column record that owns these instances
516
+ * @param factory - Factory instance for creating ServiceNow records with proper relationships
517
+ * @returns Promise that resolves to an array of all created instance records
518
+ */
519
+ async function createInstanceRecords(instancesArray: Shape[], columnId: RecordId, factory: Factory): Promise<Record[]> {
520
+ const records: Record[] = []
521
+
522
+ for (let index = 0; index < instancesArray.length; index++) {
523
+ const instanceShape = instancesArray[index]
524
+ if (instanceShape?.isObject()) {
525
+ const instance = instanceShape.asObject()
526
+
527
+ // Process roles if they exist as an array
528
+ const rolesString = getRolesString(instance.get('roles'))
529
+
530
+ const instanceRecord = await factory.createRecord({
531
+ source: instanceShape,
532
+ table: 'sp_instance',
533
+ explicitId: instance.get('$id'),
534
+ properties: instance.transform(({ $ }) => ({
535
+ sp_column: $.val(columnId),
536
+ title: $.from('title').def(''),
537
+ id: $.from('id').def(''),
538
+ sp_widget: $.from('widget').map(toReference).def(''),
539
+ widget_parameters: $.from('widgetParameters').def(''),
540
+ short_description: $.from('shortDescription').def(''),
541
+ css: $.from('css').def(''),
542
+ url: $.from('url').def(''),
543
+ glyph: $.from('glyph').def(''),
544
+ size: $.from('size').def('md'),
545
+ color: $.from('color').def('default'),
546
+ class_name: $.from('cssClass').def(''),
547
+ active: $.from('active').def(defaultValues.instance.active),
548
+ order: $.from('order').def(index + 1),
549
+ roles: $.val(rolesString).def(''),
550
+ async_load: $.from('asyncLoad').def(false),
551
+ async_load_trigger: $.from('asyncLoadTrigger').def('viewport'),
552
+ async_load_device_type: $.def(defaultValues.instance.asyncLoadDeviceType),
553
+ preserve_placeholder_size: $.def(false),
554
+ placeholder_dimensions: $.def(default_placeholder_dimensions),
555
+ advanced_placeholder_dimensions: $.def(false),
556
+ placeholder_dimensions_script: $.def(default_placeholder_script),
557
+ placeholder_template: $.def(default_placeholder_template),
558
+ })),
559
+ })
560
+
561
+ records.push(instanceRecord)
562
+ }
563
+ }
564
+ return records
565
+ }
566
+
567
+ export const SPPagePlugin = Plugin.create({
568
+ name: 'SPPagePlugin',
569
+ docs: [createSdkDocEntry('SPPage', ['sp_page'])],
570
+ records: {
571
+ sp_page: {
572
+ coalesce: ['id'],
573
+ relationships: {
574
+ sp_container: {
575
+ via: 'sp_page',
576
+ descendant: true,
577
+ relationships: {
578
+ sp_row: {
579
+ via: 'sp_container',
580
+ descendant: true,
581
+ relationships: {
582
+ sp_column: {
583
+ via: 'sp_row',
584
+ descendant: true,
585
+ relationships: {
586
+ sp_instance: {
587
+ via: 'sp_column',
588
+ descendant: true,
589
+ },
590
+ sp_row: {
591
+ via: 'sp_column',
592
+ descendant: true,
593
+ },
594
+ },
595
+ },
596
+ },
597
+ },
598
+ },
599
+ },
600
+ },
601
+ toShape(record, { descendants }) {
602
+ // Build hierarchical structure from descendants in a single pass
603
+ // to avoid "node that was removed or forgotten" errors
604
+
605
+ // Get all descendants at once to avoid multiple queries
606
+ const allContainers = sortByOrder(
607
+ descendants
608
+ .query('sp_container')
609
+ .filter((container) => container.get('sp_page').equals(record.getId()))
610
+ )
611
+ const allRows = descendants.query('sp_row')
612
+ const allColumns = descendants.query('sp_column')
613
+ const allInstances = descendants.query('sp_instance')
614
+
615
+ const containers = allContainers.map((container) => {
616
+ const containerId = container.getId()
617
+
618
+ // Get rows for this container
619
+ const containerRows = sortByOrder(
620
+ allRows.filter((row) => row.get('sp_container').equals(containerId))
621
+ )
622
+
623
+ const rows = containerRows
624
+ .map((row) => {
625
+ const rowId = row.getId()
626
+
627
+ // Get columns for this row
628
+ const rowColumns = sortByOrder(
629
+ allColumns.filter((column) => column.get('sp_row').equals(rowId))
630
+ )
631
+ const columns = rowColumns
632
+ .map((column) => {
633
+ const columnId = column.getId()
634
+
635
+ // Get instances for this column
636
+ const columnInstances = sortByOrder(
637
+ allInstances.filter((instance) => instance.get('sp_column').equals(columnId))
638
+ )
639
+
640
+ const columnRows = allRows.filter((row) => row.get('sp_column').equals(columnId))
641
+ const nestedRows = getNestedRows(columnRows, allRows, allColumns, allInstances)
642
+
643
+ const instances = columnInstances
644
+ .map((instance) => {
645
+ return getInstanceObject(instance)
646
+ })
647
+ .filter((instanceObject): instanceObject is object => Boolean(instanceObject))
648
+
649
+ return getColumnObject(column, instances, nestedRows)
650
+ })
651
+ .filter((columnObject): columnObject is Dict => Boolean(columnObject))
652
+
653
+ return getRowObject(row, columns)
654
+ })
655
+ .filter((rowObject): rowObject is Dict => Boolean(rowObject))
656
+
657
+ return getContainerObject(container, rows)
658
+ })
659
+
660
+ // Process roles to check if they should be included
661
+ const rolesArray = getRolesArray(record)
662
+
663
+ return {
664
+ success: true,
665
+ value: new CallExpressionShape({
666
+ source: record,
667
+ callee: 'SPPage',
668
+ args: [
669
+ record.transform(({ $ }) => {
670
+ const pageObject: { [key: string]: typeof $ | undefined } = {
671
+ title: $,
672
+ category: $,
673
+ pageId: $.from('id').def(''),
674
+ draft: $.toBoolean().def(false),
675
+ internal: $.toBoolean().def(false),
676
+ omitWatcher: $.from('omit_watcher').toBoolean().def(false),
677
+ public: $.toBoolean().def(false),
678
+ useSeoScript: $.from('use_seo_script').toBoolean().def(false),
679
+ css: $.def(''),
680
+ shortDescription: $.from('short_description').def(''),
681
+ seoScript: $.from('seo_script').def(''),
682
+ dynamicTitleStructure: $.from('dynamic_title_structure').def(''),
683
+ humanReadableUrlStructure: $.from('human_readable_url_structure').def(''),
684
+ }
685
+ // Only add roles if they exist
686
+ if (rolesArray) {
687
+ pageObject['roles'] = $.val(rolesArray)
688
+ }
689
+
690
+ // Only add containers if they exist
691
+ if (containers.length > 0) {
692
+ pageObject['containers'] = $.val(containers)
693
+ }
694
+
695
+ return pageObject
696
+ }),
697
+ ],
698
+ }),
699
+ }
700
+ },
701
+ },
702
+ },
703
+ shapes: [
704
+ {
705
+ shape: CallExpressionShape,
706
+ fileTypes: ['fluent'],
707
+ async toRecord(callExpression, { diagnostics, factory }) {
708
+ if (callExpression.getCallee() !== 'SPPage') {
709
+ return { success: false }
710
+ }
711
+
712
+ const page = callExpression.getArgument(0).asObject()
713
+ const containers = page.get('containers').ifArray()?.getElements() || []
714
+
715
+ const useSeoScriptShape = page.get('useSeoScript')
716
+ const useSeoScript = useSeoScriptShape.ifBoolean()?.getValue() ?? false
717
+ const seoScriptRef = toReference(page.get('seoScript'))
718
+ const seoScript = typeof seoScriptRef === 'string' ? seoScriptRef : seoScriptRef.getValue()
719
+
720
+ if (useSeoScript && (!seoScript || seoScript.trim() === '')) {
721
+ diagnostics.error(
722
+ useSeoScriptShape.getOriginalNode(),
723
+ `Invalid SPPage configuration: when "useSeoScript" is true, "seoScript" must be added.`
724
+ )
725
+ }
726
+
727
+ // Process roles if they exist as an array
728
+ const rolesString = getRolesString(page.get('roles'))
729
+
730
+ const pageIdShape = page.get('pageId')
731
+ const pageId = pageIdShape.asString().getValue()
732
+
733
+ if (!pageId.trim()) {
734
+ diagnostics.error(
735
+ pageIdShape.getOriginalNode(),
736
+ 'Invalid SPPage configuration: "pageId" must be a non-empty string.'
737
+ )
738
+ }
739
+
740
+ const urlStructureShape = page.get('humanReadableUrlStructure')
741
+ const urlStructure = urlStructureShape.ifString()?.getValue()
742
+ if (urlStructure && urlStructure.length > 0) {
743
+ const delimiter = String(Math.floor(Math.random() * 100000))
744
+ const urlStrings = urlStructure
745
+ .replaceAll(/[/-]/g, delimiter)
746
+ .replaceAll('%', delimiter + '%')
747
+ .split(delimiter)
748
+ const nonVariables: string[] = []
749
+ for (const segment of urlStrings) {
750
+ if (segment.length > 1 && segment.indexOf('%') === 0) {
751
+ continue
752
+ }
753
+ nonVariables.push(segment)
754
+ }
755
+
756
+ if (!/^[a-zA-Z0-9/-]*$/.test(nonVariables.join(''))) {
757
+ diagnostics.error(
758
+ urlStructureShape.getOriginalNode(),
759
+ `Only alphanumeric characters, - and / are allowed in humanReadableUrlStructure.`
760
+ )
761
+ }
762
+
763
+ if (urlStructure.indexOf('/') !== urlStructure.lastIndexOf('/')) {
764
+ diagnostics.error(
765
+ urlStructureShape.getOriginalNode(),
766
+ `No more than one "/" character is allowed in humanReadableUrlStructure.`
767
+ )
768
+ }
769
+ }
770
+
771
+ const title = page.get('title').ifString()?.getValue() || pageId
772
+
773
+ // Create the main page record
774
+ const pageRecord = await factory.createRecord({
775
+ source: callExpression,
776
+ table: 'sp_page',
777
+ properties: page.transform(({ $ }) => ({
778
+ id: $.from('pageId'),
779
+ title: $.val(title),
780
+ category: $.def('custom'),
781
+ css: $.def(''),
782
+ draft: $.def(false),
783
+ dynamic_title_structure: $.from('dynamicTitleStructure').def(''),
784
+ human_readable_url_structure: $.from('humanReadableUrlStructure').def(''),
785
+ internal: $.def(false),
786
+ omit_watcher: $.from('omitWatcher').def(false),
787
+ public: $.def(false),
788
+ roles: $.val(rolesString).def(''),
789
+ seo_script: $.from('seoScript').map(toReference).def(''),
790
+ short_description: $.from('shortDescription').def(''),
791
+ use_seo_script: $.from('useSeoScript').def(false),
792
+ })),
793
+ })
794
+
795
+ // Create container, row, column, and instance records
796
+ const allRecords = await createContainerRecords(containers, pageRecord.getId(), title, factory)
797
+
798
+ return {
799
+ success: true,
800
+ value: pageRecord.with(...allRecords),
801
+ }
802
+ },
803
+ },
804
+ ],
805
+ })