@servicenow/sdk-build-plugins 4.6.0 → 4.7.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 (286) hide show
  1. package/dist/acl-plugin.js +3 -4
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/applicability-plugin.js +0 -2
  4. package/dist/applicability-plugin.js.map +1 -1
  5. package/dist/application-menu-plugin.js +0 -2
  6. package/dist/application-menu-plugin.js.map +1 -1
  7. package/dist/arrow-function-plugin.js +0 -1
  8. package/dist/arrow-function-plugin.js.map +1 -1
  9. package/dist/atf/test-plugin.js +6 -10
  10. package/dist/atf/test-plugin.js.map +1 -1
  11. package/dist/basic-syntax-plugin.js +10 -4
  12. package/dist/basic-syntax-plugin.js.map +1 -1
  13. package/dist/business-rule-plugin.js +0 -1
  14. package/dist/business-rule-plugin.js.map +1 -1
  15. package/dist/call-expression-plugin.js +0 -1
  16. package/dist/call-expression-plugin.js.map +1 -1
  17. package/dist/claims-plugin.js +0 -1
  18. package/dist/claims-plugin.js.map +1 -1
  19. package/dist/client-script-plugin.js +0 -1
  20. package/dist/client-script-plugin.js.map +1 -1
  21. package/dist/column-plugin.js +120 -49
  22. package/dist/column-plugin.js.map +1 -1
  23. package/dist/cross-scope-privilege-plugin.js +0 -1
  24. package/dist/cross-scope-privilege-plugin.js.map +1 -1
  25. package/dist/dashboard/dashboard-plugin.js +0 -2
  26. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  27. package/dist/data-plugin.js +0 -1
  28. package/dist/data-plugin.js.map +1 -1
  29. package/dist/data-policy-plugin.d.ts +2 -0
  30. package/dist/data-policy-plugin.js +276 -0
  31. package/dist/data-policy-plugin.js.map +1 -0
  32. package/dist/email-notification-plugin.js +2 -3
  33. package/dist/email-notification-plugin.js.map +1 -1
  34. package/dist/flow/flow-logic/flow-logic-constants.d.ts +2 -0
  35. package/dist/flow/flow-logic/flow-logic-constants.js +6 -1
  36. package/dist/flow/flow-logic/flow-logic-constants.js.map +1 -1
  37. package/dist/flow/flow-logic/flow-logic-diagnostics.js +192 -56
  38. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  39. package/dist/flow/flow-logic/flow-logic-plugin-helpers.d.ts +2 -1
  40. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js +44 -5
  41. package/dist/flow/flow-logic/flow-logic-plugin-helpers.js.map +1 -1
  42. package/dist/flow/flow-logic/flow-logic-plugin.js +279 -29
  43. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  44. package/dist/flow/flow-logic/flow-logic-shapes.d.ts +15 -0
  45. package/dist/flow/flow-logic/flow-logic-shapes.js +25 -1
  46. package/dist/flow/flow-logic/flow-logic-shapes.js.map +1 -1
  47. package/dist/flow/plugins/approval-rules-plugin.js +0 -1
  48. package/dist/flow/plugins/approval-rules-plugin.js.map +1 -1
  49. package/dist/flow/plugins/flow-action-definition-plugin.js +804 -205
  50. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  51. package/dist/flow/plugins/flow-data-pill-plugin.js +3 -5
  52. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  53. package/dist/flow/plugins/flow-definition-plugin.js +84 -17
  54. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  55. package/dist/flow/plugins/flow-diagnostics-plugin.js +65 -3
  56. package/dist/flow/plugins/flow-diagnostics-plugin.js.map +1 -1
  57. package/dist/flow/plugins/flow-instance-plugin.js +13 -5
  58. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  59. package/dist/flow/plugins/flow-trigger-instance-plugin.js +0 -1
  60. package/dist/flow/plugins/flow-trigger-instance-plugin.js.map +1 -1
  61. package/dist/flow/plugins/inline-script-plugin.js +0 -1
  62. package/dist/flow/plugins/inline-script-plugin.js.map +1 -1
  63. package/dist/flow/plugins/step-definition-plugin.js +0 -2
  64. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  65. package/dist/flow/plugins/step-instance-plugin.js +216 -77
  66. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  67. package/dist/flow/plugins/trigger-plugin.js +0 -2
  68. package/dist/flow/plugins/trigger-plugin.js.map +1 -1
  69. package/dist/flow/plugins/wfa-datapill-plugin.js +0 -1
  70. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  71. package/dist/flow/utils/datapill-transformer.js +9 -5
  72. package/dist/flow/utils/datapill-transformer.js.map +1 -1
  73. package/dist/flow/utils/flow-constants.d.ts +12 -0
  74. package/dist/flow/utils/flow-constants.js +17 -3
  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 +21 -13
  78. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  79. package/dist/flow/utils/flow-pill-utils.d.ts +26 -0
  80. package/dist/flow/utils/flow-pill-utils.js +50 -0
  81. package/dist/flow/utils/flow-pill-utils.js.map +1 -0
  82. package/dist/flow/utils/flow-stage-processor.d.ts +138 -0
  83. package/dist/flow/utils/flow-stage-processor.js +665 -0
  84. package/dist/flow/utils/flow-stage-processor.js.map +1 -0
  85. package/dist/flow/utils/pill-string-parser.js +28 -43
  86. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  87. package/dist/flow/utils/utils.d.ts +11 -6
  88. package/dist/flow/utils/utils.js +37 -28
  89. package/dist/flow/utils/utils.js.map +1 -1
  90. package/dist/form-plugin.js +4 -14
  91. package/dist/form-plugin.js.map +1 -1
  92. package/dist/html-import-plugin.js +0 -1
  93. package/dist/html-import-plugin.js.map +1 -1
  94. package/dist/import-sets-plugin.js +0 -2
  95. package/dist/import-sets-plugin.js.map +1 -1
  96. package/dist/inbound-email-action-plugin.js +0 -1
  97. package/dist/inbound-email-action-plugin.js.map +1 -1
  98. package/dist/index.d.ts +2 -1
  99. package/dist/index.js +5 -1
  100. package/dist/index.js.map +1 -1
  101. package/dist/instance-scan-plugin.js +0 -7
  102. package/dist/instance-scan-plugin.js.map +1 -1
  103. package/dist/json-plugin.js +0 -1
  104. package/dist/json-plugin.js.map +1 -1
  105. package/dist/list-plugin.js +4 -1
  106. package/dist/list-plugin.js.map +1 -1
  107. package/dist/now-attach-plugin.js +0 -1
  108. package/dist/now-attach-plugin.js.map +1 -1
  109. package/dist/now-config-plugin.js +1 -1
  110. package/dist/now-config-plugin.js.map +1 -1
  111. package/dist/now-id-plugin.js +0 -1
  112. package/dist/now-id-plugin.js.map +1 -1
  113. package/dist/now-include-plugin.js +0 -1
  114. package/dist/now-include-plugin.js.map +1 -1
  115. package/dist/now-ref-plugin.js +0 -1
  116. package/dist/now-ref-plugin.js.map +1 -1
  117. package/dist/now-unresolved-plugin.js +0 -1
  118. package/dist/now-unresolved-plugin.js.map +1 -1
  119. package/dist/package-json-plugin.js +3 -2
  120. package/dist/package-json-plugin.js.map +1 -1
  121. package/dist/property-plugin.js +0 -2
  122. package/dist/property-plugin.js.map +1 -1
  123. package/dist/record-plugin.d.ts +2 -0
  124. package/dist/record-plugin.js +5 -4
  125. package/dist/record-plugin.js.map +1 -1
  126. package/dist/repack/lint/Rules.d.ts +1 -2
  127. package/dist/rest-api-plugin.js +6 -5
  128. package/dist/rest-api-plugin.js.map +1 -1
  129. package/dist/role-plugin.js +0 -1
  130. package/dist/role-plugin.js.map +1 -1
  131. package/dist/schedule-script/scheduled-script-plugin.js +5 -4
  132. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
  133. package/dist/script-action-plugin.js +0 -2
  134. package/dist/script-action-plugin.js.map +1 -1
  135. package/dist/script-include-plugin.js +4 -4
  136. package/dist/script-include-plugin.js.map +1 -1
  137. package/dist/server-module-plugin/index.js +2 -3
  138. package/dist/server-module-plugin/index.js.map +1 -1
  139. package/dist/service-catalog/catalog-clientscript-plugin.js +2 -4
  140. package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
  141. package/dist/service-catalog/catalog-item-plugin.js +0 -2
  142. package/dist/service-catalog/catalog-item-plugin.js.map +1 -1
  143. package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -4
  144. package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
  145. package/dist/service-catalog/sc-record-producer-plugin.js +0 -2
  146. package/dist/service-catalog/sc-record-producer-plugin.js.map +1 -1
  147. package/dist/service-catalog/service-catalog-base.d.ts +2 -2
  148. package/dist/service-catalog/service-catalog-base.js +2 -2
  149. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  150. package/dist/service-catalog/utils.js +1 -1
  151. package/dist/service-catalog/utils.js.map +1 -1
  152. package/dist/service-catalog/variable-set-plugin.js +0 -2
  153. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  154. package/dist/service-portal/angular-provider-plugin.js +0 -2
  155. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  156. package/dist/service-portal/dependency-plugin.js +3 -5
  157. package/dist/service-portal/dependency-plugin.js.map +1 -1
  158. package/dist/service-portal/header-footer-plugin.js +3 -5
  159. package/dist/service-portal/header-footer-plugin.js.map +1 -1
  160. package/dist/service-portal/menu-plugin.js +0 -1
  161. package/dist/service-portal/menu-plugin.js.map +1 -1
  162. package/dist/service-portal/page-plugin.js +0 -1
  163. package/dist/service-portal/page-plugin.js.map +1 -1
  164. package/dist/service-portal/page-route-map-plugin.js +0 -1
  165. package/dist/service-portal/page-route-map-plugin.js.map +1 -1
  166. package/dist/service-portal/portal-plugin.js +0 -2
  167. package/dist/service-portal/portal-plugin.js.map +1 -1
  168. package/dist/service-portal/theme-plugin.js +0 -2
  169. package/dist/service-portal/theme-plugin.js.map +1 -1
  170. package/dist/service-portal/widget-plugin.js +3 -5
  171. package/dist/service-portal/widget-plugin.js.map +1 -1
  172. package/dist/sla-plugin.js +0 -2
  173. package/dist/sla-plugin.js.map +1 -1
  174. package/dist/static-content-plugin.js +32 -3
  175. package/dist/static-content-plugin.js.map +1 -1
  176. package/dist/table-plugin.js +303 -66
  177. package/dist/table-plugin.js.map +1 -1
  178. package/dist/ui-action-plugin.js +26 -17
  179. package/dist/ui-action-plugin.js.map +1 -1
  180. package/dist/ui-page-plugin.js +159 -17
  181. package/dist/ui-page-plugin.js.map +1 -1
  182. package/dist/ui-policy-plugin.js +28 -97
  183. package/dist/ui-policy-plugin.js.map +1 -1
  184. package/dist/user-preference-plugin.js +0 -2
  185. package/dist/user-preference-plugin.js.map +1 -1
  186. package/dist/utils.d.ts +5 -9
  187. package/dist/utils.js +38 -11
  188. package/dist/utils.js.map +1 -1
  189. package/dist/ux-list-menu-config-plugin.js +0 -2
  190. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  191. package/dist/view-plugin.js +0 -1
  192. package/dist/view-plugin.js.map +1 -1
  193. package/dist/workspace-plugin.js +0 -2
  194. package/dist/workspace-plugin.js.map +1 -1
  195. package/package.json +6 -6
  196. package/src/acl-plugin.ts +4 -5
  197. package/src/applicability-plugin.ts +0 -2
  198. package/src/application-menu-plugin.ts +0 -2
  199. package/src/arrow-function-plugin.ts +0 -1
  200. package/src/atf/test-plugin.ts +6 -11
  201. package/src/basic-syntax-plugin.ts +11 -4
  202. package/src/business-rule-plugin.ts +1 -2
  203. package/src/call-expression-plugin.ts +0 -1
  204. package/src/claims-plugin.ts +0 -1
  205. package/src/client-script-plugin.ts +1 -2
  206. package/src/column-plugin.ts +163 -76
  207. package/src/cross-scope-privilege-plugin.ts +1 -2
  208. package/src/dashboard/dashboard-plugin.ts +0 -2
  209. package/src/data-plugin.ts +0 -1
  210. package/src/data-policy-plugin.ts +333 -0
  211. package/src/email-notification-plugin.ts +8 -4
  212. package/src/flow/flow-logic/flow-logic-constants.ts +6 -0
  213. package/src/flow/flow-logic/flow-logic-diagnostics.ts +236 -58
  214. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +59 -6
  215. package/src/flow/flow-logic/flow-logic-plugin.ts +368 -38
  216. package/src/flow/flow-logic/flow-logic-shapes.ts +25 -0
  217. package/src/flow/plugins/approval-rules-plugin.ts +0 -1
  218. package/src/flow/plugins/flow-action-definition-plugin.ts +940 -208
  219. package/src/flow/plugins/flow-data-pill-plugin.ts +3 -5
  220. package/src/flow/plugins/flow-definition-plugin.ts +159 -26
  221. package/src/flow/plugins/flow-diagnostics-plugin.ts +89 -3
  222. package/src/flow/plugins/flow-instance-plugin.ts +26 -12
  223. package/src/flow/plugins/flow-trigger-instance-plugin.ts +0 -1
  224. package/src/flow/plugins/inline-script-plugin.ts +0 -1
  225. package/src/flow/plugins/step-definition-plugin.ts +0 -2
  226. package/src/flow/plugins/step-instance-plugin.ts +259 -65
  227. package/src/flow/plugins/trigger-plugin.ts +0 -2
  228. package/src/flow/plugins/wfa-datapill-plugin.ts +0 -1
  229. package/src/flow/utils/datapill-transformer.ts +13 -5
  230. package/src/flow/utils/flow-constants.ts +19 -1
  231. package/src/flow/utils/flow-io-to-record.ts +29 -19
  232. package/src/flow/utils/flow-pill-utils.ts +48 -0
  233. package/src/flow/utils/flow-stage-processor.ts +831 -0
  234. package/src/flow/utils/pill-string-parser.ts +29 -47
  235. package/src/flow/utils/utils.ts +39 -35
  236. package/src/form-plugin.ts +5 -15
  237. package/src/html-import-plugin.ts +0 -1
  238. package/src/import-sets-plugin.ts +0 -2
  239. package/src/inbound-email-action-plugin.ts +1 -2
  240. package/src/index.ts +7 -1
  241. package/src/instance-scan-plugin.ts +0 -7
  242. package/src/json-plugin.ts +0 -1
  243. package/src/list-plugin.ts +6 -2
  244. package/src/now-attach-plugin.ts +0 -1
  245. package/src/now-config-plugin.ts +1 -1
  246. package/src/now-id-plugin.ts +0 -1
  247. package/src/now-include-plugin.ts +0 -1
  248. package/src/now-ref-plugin.ts +0 -1
  249. package/src/now-unresolved-plugin.ts +0 -1
  250. package/src/package-json-plugin.ts +8 -3
  251. package/src/property-plugin.ts +0 -2
  252. package/src/record-plugin.ts +14 -6
  253. package/src/repack/lint/Rules.ts +1 -1
  254. package/src/rest-api-plugin.ts +7 -6
  255. package/src/role-plugin.ts +1 -2
  256. package/src/schedule-script/scheduled-script-plugin.ts +11 -5
  257. package/src/script-action-plugin.ts +0 -2
  258. package/src/script-include-plugin.ts +8 -4
  259. package/src/server-module-plugin/index.ts +2 -3
  260. package/src/service-catalog/catalog-clientscript-plugin.ts +2 -4
  261. package/src/service-catalog/catalog-item-plugin.ts +0 -2
  262. package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -4
  263. package/src/service-catalog/sc-record-producer-plugin.ts +0 -2
  264. package/src/service-catalog/service-catalog-base.ts +2 -2
  265. package/src/service-catalog/utils.ts +1 -1
  266. package/src/service-catalog/variable-set-plugin.ts +0 -2
  267. package/src/service-portal/angular-provider-plugin.ts +0 -2
  268. package/src/service-portal/dependency-plugin.ts +0 -2
  269. package/src/service-portal/header-footer-plugin.ts +0 -2
  270. package/src/service-portal/menu-plugin.ts +1 -2
  271. package/src/service-portal/page-plugin.ts +1 -2
  272. package/src/service-portal/page-route-map-plugin.ts +1 -2
  273. package/src/service-portal/portal-plugin.ts +0 -2
  274. package/src/service-portal/theme-plugin.ts +0 -2
  275. package/src/service-portal/widget-plugin.ts +0 -2
  276. package/src/sla-plugin.ts +0 -2
  277. package/src/static-content-plugin.ts +37 -4
  278. package/src/table-plugin.ts +371 -92
  279. package/src/ui-action-plugin.ts +30 -17
  280. package/src/ui-page-plugin.ts +188 -20
  281. package/src/ui-policy-plugin.ts +33 -130
  282. package/src/user-preference-plugin.ts +0 -2
  283. package/src/utils.ts +48 -11
  284. package/src/ux-list-menu-config-plugin.ts +0 -2
  285. package/src/view-plugin.ts +0 -1
  286. package/src/workspace-plugin.ts +0 -2
@@ -6,13 +6,12 @@ import {
6
6
  type Shape,
7
7
  type StringShape,
8
8
  } from '@servicenow/sdk-build-core'
9
+ import type { UiAction } from '@servicenow/sdk-core/runtime/ui'
9
10
  import { NowIdShape } from './now-id-plugin'
10
11
  import { ModuleFunctionShape } from './server-module-plugin'
11
- import { createSdkDocEntry } from './utils'
12
12
 
13
13
  export const UiActionPlugin = Plugin.create({
14
14
  name: 'UiActionPlugin',
15
- docs: [createSdkDocEntry('UiAction', ['sys_ui_action'])],
16
15
  records: {
17
16
  sys_ui_action: {
18
17
  relationships: {
@@ -161,7 +160,7 @@ export const UiActionPlugin = Plugin.create({
161
160
  order: $.map((o) => (o.ifString()?.getValue() === '' ? 100 : o))
162
161
  .toNumber()
163
162
  .def(100),
164
- overrides: $.def(''),
163
+ overrides: $.from('sys_overrides').def(''),
165
164
  showQuery: $.from('show_query').toBoolean().def(false),
166
165
  showUpdate: $.from('show_update').toBoolean(),
167
166
  showInsert: $.from('show_insert').toBoolean(),
@@ -195,6 +194,18 @@ export const UiActionPlugin = Plugin.create({
195
194
  const arg = callExpression.getArgument(0).asObject()
196
195
  const isClient = arg.get(['client', 'isClient'])
197
196
 
197
+ const form = arg.get('form')?.getValue() as UiAction<string>['form']
198
+ const isFormAction = form?.showButton || form?.showLink || form?.showContextMenu
199
+
200
+ const list = arg.get('list').getValue() as UiAction<string>['list']
201
+ const isListAction =
202
+ list?.showButton ||
203
+ list?.showBannerButton ||
204
+ list?.showContextMenu ||
205
+ list?.showListChoice ||
206
+ list?.showLink ||
207
+ list?.showSaveWithFormButton
208
+
198
209
  const uiAction = await factory.createRecord({
199
210
  source: callExpression,
200
211
  table: 'sys_ui_action',
@@ -212,8 +223,8 @@ export const UiActionPlugin = Plugin.create({
212
223
  ? arg.get('messages').asArray().getValue().join('\n')
213
224
  : arg.get('messages')
214
225
  ).def([]),
215
- list_style: $.val(arg.get(['list', 'style'])),
216
- form_style: $.val(arg.get(['form', 'style'])),
226
+ list_style: $.val(list?.style),
227
+ form_style: $.val(form?.style),
217
228
  condition: $.def(''),
218
229
  script: $.map(
219
230
  (v) => v.if(ModuleFunctionShape)?.toString((n) => `${n}({{PARAMS}})`, ['current']) ?? v
@@ -222,24 +233,26 @@ export const UiActionPlugin = Plugin.create({
222
233
  .toCdata(),
223
234
  hint: $.def(''),
224
235
  order: $.toNumber().def(100),
225
- overrides: $.def(''),
226
- form_button: $.val(arg.get(['form', 'showButton'])).def(false),
227
- list_button: $.val(arg.get(['list', 'showButton'])).def(false),
228
- form_link: $.val(arg.get(['form', 'showLink'])).def(false),
229
- list_link: $.val(arg.get(['list', 'showLink'])).def(false),
230
- form_context_menu: $.val(arg.get(['form', 'showContextMenu'])).def(false),
231
- list_context_menu: $.val(arg.get(['list', 'showContextMenu'])).def(false),
236
+ sys_overrides: $.from('overrides').def(''),
237
+ form_action: $.val(isFormAction).def(false),
238
+ list_action: $.val(isListAction).def(false),
239
+ form_button: $.val(form?.showButton).def(false),
240
+ list_button: $.val(list?.showButton).def(false),
241
+ form_link: $.val(form?.showLink).def(false),
242
+ list_link: $.val(list?.showLink).def(false),
243
+ form_context_menu: $.val(form?.showContextMenu).def(false),
244
+ list_context_menu: $.val(list?.showContextMenu).def(false),
232
245
  show_query: $.from('showQuery').toBoolean().def(false),
233
246
  show_insert: $.from('showInsert').toBoolean().def(false).def(true),
234
247
  show_multiple_update: $.from('showMultipleUpdate').toBoolean().def(false),
235
248
  show_update: $.from('showUpdate').toBoolean().def(true),
236
- list_choice: $.val(arg.get(['list', 'showListChoice'])).def(false),
237
- list_banner_button: $.val(arg.get(['list', 'showBannerButton'])).def(false),
238
- list_save_with_form_button: $.val(arg.get(['list', 'showSaveWithFormButton'])).def(false),
249
+ list_choice: $.val(list?.showListChoice).def(false),
250
+ list_banner_button: $.val(list?.showBannerButton).def(false),
251
+ list_save_with_form_button: $.val(list?.showSaveWithFormButton).def(false),
239
252
  isolate_script: $.from('isolateScript').toBoolean().def(false),
240
253
  ui11_compatible: $.val(arg.get(['client', 'isUi11Compatible'])).def(false),
241
- ui16_compatible: $.val(arg.get(['client', 'isUi16Compatible'])),
242
- client_script_v2: $.val(arg.get(['workspace', 'clientScriptV2'])),
254
+ ui16_compatible: $.val(arg.get(['client', 'isUi16Compatible'])).def(false),
255
+ client_script_v2: $.val(arg.get(['workspace', 'clientScriptV2'])).toCdata(),
243
256
  form_button_v2: $.val(arg.get(['workspace', 'showFormButtonV2'])).def(false),
244
257
  form_menu_button_v2: $.val(arg.get(['workspace', 'showFormMenuButtonV2'])).def(false),
245
258
  format_for_configurable_workspace: $.val(arg.get(['workspace', 'isConfigurableWorkspace'])).def(
@@ -8,6 +8,7 @@ import {
8
8
  isSNScope,
9
9
  zipSync,
10
10
  unzipSync,
11
+ ts,
11
12
  type Logger,
12
13
  type Diagnostics,
13
14
  type Project,
@@ -21,9 +22,10 @@ import { parseDocument, DomUtils } from 'htmlparser2'
21
22
  import { XMLParser, XMLBuilder, type X2jOptions, type XmlBuilderOptions } from 'fast-xml-parser'
22
23
  import { create } from 'xmlbuilder2'
23
24
  import { NowIdShape } from './now-id-plugin'
25
+ import { NowIncludeShape } from './now-include-plugin'
24
26
  import { CHUNK_SIZE, chunkData, generateId } from './static-content-plugin'
25
27
  import { sha256 } from './now-attach-plugin'
26
- import { createSdkDocEntry } from './utils'
28
+ import { TRANSLATIONS_SUFFIX } from '@servicenow/isomorphic-rollup'
27
29
 
28
30
  const parserOptions: X2jOptions = {
29
31
  ignoreAttributes: false,
@@ -157,6 +159,50 @@ const SOURCE_ARTIFACT_RELATIONSHIPS = {
157
159
 
158
160
  const parser = new XMLParser(parserOptions)
159
161
 
162
+ const escapeHtml = (s: string): string => s.replace(/&/g, '&amp;')
163
+
164
+ const escapeSingle = (s: string): string => escapeHtml(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'")
165
+
166
+ const escapeDouble = (s: string): string => escapeHtml(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
167
+
168
+ const getTranslationMessages = (
169
+ fs: FileSystem,
170
+ config: NowConfig,
171
+ rootDir: string,
172
+ htmlRelPathFromClientDir: string
173
+ ): string[] => {
174
+ // The rollup plugin emits `<stem>.translations.json` next to each bundled html, preserving
175
+ // the html's subdirectory structure under clientDir. So a source html at
176
+ // `<clientDir>/admin/page.html` produces `<staticContentDir>/admin/page.translations.json`.
177
+ if (htmlRelPathFromClientDir.startsWith('..')) {
178
+ return []
179
+ }
180
+ const translationsRelPath = htmlRelPathFromClientDir.replace(/\.html$/, TRANSLATIONS_SUFFIX)
181
+ const translationsPath = path.join(rootDir, config.staticContentDir, translationsRelPath)
182
+ try {
183
+ fs.accessSync(translationsPath)
184
+ } catch {
185
+ return []
186
+ }
187
+ try {
188
+ const content = fs.readFileSync(translationsPath, { encoding: 'utf-8' })
189
+ const parsed = JSON.parse(content) as { messages: Array<string | { [key: string]: string }> }
190
+ return parsed.messages.map((entry) => (typeof entry === 'string' ? entry : (entry['code'] ?? '')))
191
+ } catch {
192
+ return []
193
+ }
194
+ }
195
+
196
+ const translationsJellyScript = (scope: string, messages: string[]): string => `
197
+ <script type="text/javascript">
198
+ 'use strict';
199
+
200
+ window.__TRANSLATIONS__ = {
201
+ ${messages.map((message) => `'${escapeSingle(message)}': '\${NS,JS:sn_i18n.Message.getMessage("${escapeDouble(scope)}", "${escapeDouble(message)}")}'`).join(',\n ')}
202
+ };
203
+ </script>
204
+ `
205
+
160
206
  // TODO: Remove this shim tag once we've shipped Glide support for this feature.
161
207
  const nowUxGlobals = (themeId: string = POLARIS_APPSHELL_THEME_ID) => {
162
208
  return parser.parse(`<!-- @sdk:now-ux-globals -->
@@ -216,7 +262,6 @@ const nodeTransformer = (nodes: any[]) => {
216
262
 
217
263
  export const UiPagePlugin = Plugin.create({
218
264
  name: 'UiPagePlugin',
219
- docs: [createSdkDocEntry('UiPage', ['sys_ui_page'])],
220
265
  records: {
221
266
  sys_ui_page: {
222
267
  composite: true,
@@ -271,7 +316,7 @@ export const UiPagePlugin = Plugin.create({
271
316
  }
272
317
  },
273
318
 
274
- async toFile(uiPage, { config, descendants, transform }) {
319
+ async toFile(uiPage, { config, database, descendants, transform }) {
275
320
  if (!uiPage.has('endpoint') && !uiPage.has('name')) {
276
321
  return { success: false }
277
322
  }
@@ -296,6 +341,7 @@ export const UiPagePlugin = Plugin.create({
296
341
 
297
342
  const result = await serializeWithArtifact(uiPage, uiPagePropsToSerialize, {
298
343
  config,
344
+ database,
299
345
  descendants,
300
346
  transform,
301
347
  })
@@ -410,9 +456,29 @@ export const UiPagePlugin = Plugin.create({
410
456
  diagnostics.error(endpoint.getOriginalNode(), 'endpoint must include a page name before .do')
411
457
  return { success: false }
412
458
  }
413
- let html = arg.get('html').toString().getValue()
459
+ const htmlShape = arg.get('html')
460
+ let html = htmlShape.toString().getValue()
414
461
  let sourceFilePaths: string[] = []
415
462
  let assetNames: string[] = []
463
+ let vendorAssetNames: string[] = []
464
+ let htmlRelPathFromClientDir: string | undefined
465
+
466
+ const clientAbsDir = path.join(project.getRootDir(), config.clientDir)
467
+ const computeRelFromClientDir = (htmlIncludePath: string): string => {
468
+ const sourceFileDir = path.dirname(callExpression.getOriginalNode().getSourceFile().getFilePath())
469
+ const absoluteHtmlPath = path.resolve(sourceFileDir, htmlIncludePath)
470
+ return path.relative(clientAbsDir, absoluteHtmlPath).replace(/\\/g, '/')
471
+ }
472
+
473
+ // `html: Now.include('../ui/hello.html')` resolves to a NowIncludeShape whose path
474
+ // we can read directly. The included content has no HTML_IMPORT_PREFIX, so detect
475
+ // this case before the prefix check below.
476
+ if (htmlShape.is(NowIncludeShape)) {
477
+ const includePath = htmlShape.as(NowIncludeShape).getPath()
478
+ if (includePath.endsWith('.html')) {
479
+ htmlRelPathFromClientDir = computeRelFromClientDir(includePath)
480
+ }
481
+ }
416
482
 
417
483
  // HtmlImportPlugin (which runs before toRecord) resolves `html: _html` identifiers
418
484
  // to the file content and prepends an HTML_IMPORT_PREFIX warning comment.
@@ -421,16 +487,23 @@ export const UiPagePlugin = Plugin.create({
421
487
  // An inline string (e.g. `html: '<h1>...</h1>'`) never gets the prefix, so an
422
488
  // unrelated `.html` import in the same file does NOT trigger artifact creation.
423
489
  if (html.trimStart().startsWith(HTML_IMPORT_PREFIX)) {
424
- const originalNode = callExpression.getOriginalNode()
425
- const sourceFile = originalNode.getSourceFile()
426
- // biome-ignore lint/suspicious/noExplicitAny: ts-morph ImportDeclaration type not exported
427
- const htmlImport = (sourceFile.getImportDeclarations() as any[]).find((imp) =>
428
- imp.getModuleSpecifierValue().endsWith('.html')
429
- )
430
- if (htmlImport) {
431
- const relativeHtmlPath = htmlImport.getModuleSpecifierValue()
432
- const sourceFileDir = path.dirname(sourceFile.getFilePath())
433
- const absoluteHtmlPath = path.resolve(sourceFileDir, relativeHtmlPath)
490
+ // Resolve the import declaration from the specific identifier passed as the
491
+ // `html` argument — NOT the first .html import in the file. A single .now.ts
492
+ // may declare several UiPages with different html imports, and each one must
493
+ // pick up only the manifest/translations for the file it actually imports.
494
+ const htmlNode = htmlShape.getOriginalNode()
495
+ const moduleSpecifier = ts.Node.isIdentifier(htmlNode)
496
+ ? htmlNode
497
+ .getSymbol()
498
+ ?.getDeclarations()[0]
499
+ ?.getParent()
500
+ ?.asKind(ts.SyntaxKind.ImportDeclaration)
501
+ ?.getModuleSpecifierValue()
502
+ : undefined
503
+ if (moduleSpecifier?.endsWith('.html')) {
504
+ htmlRelPathFromClientDir = computeRelFromClientDir(moduleSpecifier)
505
+ const sourceFileDir = path.dirname(htmlNode.getSourceFile().getFilePath())
506
+ const absoluteHtmlPath = path.resolve(sourceFileDir, moduleSpecifier)
434
507
  const manifest = getUIPageSourceFilePaths(
435
508
  absoluteHtmlPath,
436
509
  fs,
@@ -440,6 +513,7 @@ export const UiPagePlugin = Plugin.create({
440
513
  )
441
514
  sourceFilePaths = manifest.files
442
515
  assetNames = manifest.assetNames
516
+ vendorAssetNames = manifest.vendorAssetNames
443
517
  }
444
518
  }
445
519
 
@@ -458,6 +532,17 @@ export const UiPagePlugin = Plugin.create({
458
532
  }
459
533
  return { success: false }
460
534
  }
535
+ if (htmlRelPathFromClientDir) {
536
+ const messages = getTranslationMessages(
537
+ fs,
538
+ config,
539
+ project.getRootDir(),
540
+ htmlRelPathFromClientDir
541
+ )
542
+ if (messages.length > 0) {
543
+ html += translationsJellyScript(config.scope, messages)
544
+ }
545
+ }
461
546
  }
462
547
 
463
548
  const record = await factory.createRecord({
@@ -503,6 +588,28 @@ export const UiPagePlugin = Plugin.create({
503
588
  assetNames
504
589
  )
505
590
 
591
+ // Link this page's source artifact to each vendor chunk it depends on.
592
+ // Emitted for both configuration and package builds: configuration
593
+ // deploys need the M2Ms so the platform knows which vendor chunks belong
594
+ // to this page; package builds need them so vendor refs are tracked in
595
+ // keys.ts and not flagged as deletes on the next sync.
596
+ if (vendorAssetNames.length > 0) {
597
+ const artifactRecord = sourceArtifactRecords.find(
598
+ (r) => r.getTable() === 'sn_glider_source_artifact'
599
+ )
600
+ if (artifactRecord) {
601
+ const vendorM2ms = await Promise.all(
602
+ vendorAssetNames.map((name) =>
603
+ createAssetArtifactM2mRecord(artifactRecord, name, record, {
604
+ factory,
605
+ config,
606
+ })
607
+ )
608
+ )
609
+ sourceArtifactRecords.push(...vendorM2ms)
610
+ }
611
+ }
612
+
506
613
  if (sourceArtifactRecords.length > 0) {
507
614
  record.with(...sourceArtifactRecords)
508
615
  } else {
@@ -523,17 +630,18 @@ export const UiPagePlugin = Plugin.create({
523
630
  })
524
631
 
525
632
  /**
526
- * Reads source file paths from the UI source manifest file.
633
+ * Reads source file paths and vendor asset names from the UI source manifest file.
527
634
  *
528
635
  * The manifest is generated by isomorphic-rollup's sourceManifest plugin during build.
529
- * It's a JSON file with the structure: { html, entry, files: string[] }
636
+ * It's a JSON file with the structure:
637
+ * { html, entry, files: string[], vendors: [fileName, contentHash][] }
530
638
  *
531
639
  * @param htmlFilePath - Path to the HTML file
532
640
  * @param fs - File system interface
533
641
  * @param logger - Logger for diagnostics
534
642
  * @param config - NowConfig with staticContentDir
535
643
  * @param rootDir - Project root directory
536
- * @returns Array of source file paths (empty array if manifest not found)
644
+ * @returns Source file paths, entry asset names, and vendor asset names
537
645
  */
538
646
  const getUIPageSourceFilePaths = (
539
647
  htmlFilePath: string,
@@ -541,8 +649,8 @@ const getUIPageSourceFilePaths = (
541
649
  logger: Logger,
542
650
  config: NowConfig,
543
651
  rootDir: string
544
- ): { files: string[]; assetNames: string[] } => {
545
- const empty = { files: [], assetNames: [] }
652
+ ): { files: string[]; assetNames: string[]; vendorAssetNames: string[] } => {
653
+ const empty = { files: [], assetNames: [], vendorAssetNames: [] }
546
654
  try {
547
655
  // Derive manifest path from HTML path by mirroring the directory structure
548
656
  // from clientDir into staticContentDir and swapping the extension.
@@ -596,7 +704,30 @@ const getUIPageSourceFilePaths = (
596
704
  // no source map in this build output — skip
597
705
  }
598
706
 
599
- return { files: manifest.files, assetNames }
707
+ // Derive vendor asset names from the manifest's vendors field.
708
+ // Each vendor entry is [fileName, contentHash], e.g.
709
+ // ["vendor-react-dom--d217b640.jsdbx", "d217b640"]
710
+ // The asset name mirrors static-content-plugin's formula:
711
+ // path.join(scope, fileNameWithoutExt)
712
+ const vendorAssetNames: string[] = []
713
+ const vendors: [string, string][] = manifest.vendors ?? []
714
+ for (const [fileName] of vendors) {
715
+ const ext = path.extname(fileName)
716
+ const nameWithoutExt = fileName.substring(0, fileName.length - ext.length)
717
+ const vendorAssetName = path.join(config.scope, nameWithoutExt).replace(/\\/g, '/')
718
+ vendorAssetNames.push(vendorAssetName)
719
+
720
+ // Check for vendor sourcemap
721
+ const vendorMapPath = path.join(staticContentAbsDir, `${nameWithoutExt}.jsdbx.map`)
722
+ try {
723
+ fs.accessSync(vendorMapPath)
724
+ vendorAssetNames.push(path.join(config.scope, `${nameWithoutExt}.js.map`).replace(/\\/g, '/'))
725
+ } catch {
726
+ // no sourcemap for this vendor chunk
727
+ }
728
+ }
729
+
730
+ return { files: manifest.files, assetNames, vendorAssetNames }
600
731
  } catch (error) {
601
732
  logger.warn(`Failed to read source manifest: ${error}`)
602
733
  return empty
@@ -1114,6 +1245,7 @@ async function serializeWithArtifact(
1114
1245
  metadataRecord: Record,
1115
1246
  metadataProps: string[],
1116
1247
  context: {
1248
+ database: Database
1117
1249
  descendants: { query: (table: string) => Record[] }
1118
1250
  config: { scope: string; scopeId: string }
1119
1251
  transform: Transform
@@ -1293,6 +1425,42 @@ async function serializeWithArtifact(
1293
1425
  ? `sys_attachment.table_sys_id=${artifactId}^sys_idNOT IN${currentDocIds.join(',')}`
1294
1426
  : `sys_attachment.table_sys_id=${artifactId}`
1295
1427
  recordUpdate.ele('sys_attachment_doc', { action: 'delete_multiple', query: docQuery })
1428
+
1429
+ // Clean up stale source-artifact M2Ms for this artifact (e.g. when a vendor
1430
+ // chunk hash changes, the M2M to the prior vendor asset must be removed).
1431
+ //
1432
+ // Query the full database, not page descendants: asset M2Ms are descendants
1433
+ // of their sys_ux_lib_asset records, not of the page, so the page's
1434
+ // descendants only have the page M2M. The NOT IN list must include every
1435
+ // current-build M2M for this artifact, otherwise the sweep would also
1436
+ // delete live entry/vendor M2Ms inserted by the asset XMLs.
1437
+ //
1438
+ // Skip DELETE-action rows: on a same-project package rebuild,
1439
+ // generateRecordsFromDeletedKeys inserts DELETE rows for keys no longer
1440
+ // registered (e.g. the prior vendor M2M). Adding their sys_ids to NOT IN
1441
+ // would protect the stale rows on the platform — defeating the sweep.
1442
+ const currentM2mIds = context.database
1443
+ .query('sn_glider_source_artifact_m2m')
1444
+ .filter((m2m) => {
1445
+ if (m2m.isDeleted()) {
1446
+ return false
1447
+ }
1448
+ const srcArtifact = m2m.get('source_artifact')
1449
+ const srcId = srcArtifact.isRecord()
1450
+ ? srcArtifact.asRecord().getId().getValue()
1451
+ : srcArtifact.toString().getValue()
1452
+ return srcId === artifactId
1453
+ })
1454
+ .map((m2m) => m2m.getId().getValue())
1455
+
1456
+ const m2mQuery =
1457
+ currentM2mIds.length > 0
1458
+ ? `source_artifact=${artifactId}^sys_idNOT IN${currentM2mIds.join(',')}`
1459
+ : `source_artifact=${artifactId}`
1460
+ recordUpdate.ele('sn_glider_source_artifact_m2m', {
1461
+ action: 'delete_multiple',
1462
+ query: m2mQuery,
1463
+ })
1296
1464
  })
1297
1465
 
1298
1466
  return {
@@ -8,7 +8,7 @@ import {
8
8
  type Record,
9
9
  type Shape,
10
10
  } from '@servicenow/sdk-build-core'
11
- import { createSdkDocEntry, validateClientSideScript } from './utils'
11
+ import { validateClientSideScript } from './utils'
12
12
  import { NowIdShape } from './now-id-plugin'
13
13
 
14
14
  // Define table names as constants since they're not exported from the core tables
@@ -93,7 +93,6 @@ const validateScriptProperty = (
93
93
 
94
94
  export const UiPolicyPlugin = Plugin.create({
95
95
  name: 'UiPolicyPlugin',
96
- docs: [createSdkDocEntry('UiPolicy', ['sys_ui_policy'])],
97
96
  records: {
98
97
  [SYS_UI_POLICY]: {
99
98
  coalesce: ['table', 'short_description'],
@@ -108,128 +107,35 @@ export const UiPolicyPlugin = Plugin.create({
108
107
  },
109
108
  },
110
109
  toShape(record, { descendants }) {
111
- const actions = descendants
112
- .query(SYS_UI_POLICY_ACTION)
113
- .map((action) => {
114
- const fieldValue = action.get('field')?.ifString()?.getValue()
115
- if (!fieldValue) {
116
- return null // Skip actions without a field
117
- }
118
-
119
- const actionObj: {
120
- field: string
121
- visible?: boolean | 'ignore'
122
- readOnly?: boolean | 'ignore'
123
- mandatory?: boolean | 'ignore'
124
- cleared?: boolean
125
- table?: string
126
- value?: string
127
- fieldMessage?: string
128
- fieldMessageType?: string
129
- valueAction?: string
130
- } = {
131
- field: fieldValue,
132
- }
133
-
134
- // Convert ServiceNow string format to boolean | 'ignore'
135
- const visible = stringToBoolean(action.get('visible')?.ifString()?.getValue())
136
- const disabled = stringToBoolean(action.get('disabled')?.ifString()?.getValue())
137
- const mandatory = stringToBoolean(action.get('mandatory')?.ifString()?.getValue())
138
-
139
- // Only include visible if it's not 'ignore'
140
- if (visible !== undefined && visible !== 'ignore') {
141
- actionObj.visible = visible
142
- }
143
-
144
- // Convert 'disabled' to 'readOnly'
145
- // Only include 'readOnly' if it's not 'ignore'
146
- if (disabled !== undefined && disabled !== 'ignore') {
147
- actionObj.readOnly = disabled
148
- }
149
-
150
- // Only include mandatory if it's not 'ignore'
151
- if (mandatory !== undefined && mandatory !== 'ignore') {
152
- actionObj.mandatory = mandatory
153
- }
154
-
155
- const cleared = action.get('cleared')?.ifBoolean()?.getValue()
156
- if (cleared) {
157
- actionObj.cleared = cleared
158
- }
159
-
160
- // Add new optional fields - only if they have meaningful values
161
- const table = action.get('table')?.ifString()?.getValue()
162
- const parentTable = record.get('table')?.ifString()?.getValue()
163
- // Only include table if it's different from the parent policy's table
164
- if (table && table !== parentTable) {
165
- actionObj.table = table
166
- }
167
-
168
- const value = action.get('value')?.ifString()?.getValue()
169
- if (value) {
170
- actionObj.value = value
171
- }
172
-
173
- const fieldMessage = action.get('field_message')?.ifString()?.getValue()
174
- if (fieldMessage) {
175
- actionObj.fieldMessage = fieldMessage
176
- }
177
-
178
- const fieldMessageType = action.get('field_message_type')?.ifString()?.getValue()
179
- // Only include if it's not the default value 'none'
180
- if (fieldMessageType && fieldMessageType !== 'none') {
181
- actionObj.fieldMessageType = fieldMessageType
182
- }
183
-
184
- const valueAction = action.get('value_action')?.ifString()?.getValue()
185
- // Only include if it's not the default value 'ignore'
186
- if (valueAction && valueAction !== 'ignore') {
187
- actionObj.valueAction = valueAction
188
- }
189
-
190
- // Skip actions that have no meaningful properties (all are 'ignore')
191
- if (Object.keys(actionObj).length === 1) {
192
- return null // Only 'field' property exists, skip this action
193
- }
110
+ const actions = descendants.query(SYS_UI_POLICY_ACTION).map((action) => {
111
+ return action.transform(({ $ }) => ({
112
+ field: $,
113
+ visible: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
114
+ readOnly: $.from('disabled')
115
+ .map((v) => stringToBoolean(v?.asString()?.getValue()))
116
+ .def('ignore'),
117
+ mandatory: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
118
+ cleared: $.toBoolean().def(false),
119
+ table: $.def(''),
120
+ value: $.def(''),
121
+ fieldMessage: $.from('field_message').def(''),
122
+ fieldMessageType: $.from('field_message_type').def('none'),
123
+ valueAction: $.from('value_action').def('ignore'),
124
+ }))
125
+ })
194
126
 
195
- return actionObj
196
- })
197
- .filter((action) => action !== null) // Remove null actions
198
-
199
- // Process related list actions
200
- const relatedListActions = descendants
201
- .query(SYS_UI_POLICY_RL_ACTION)
202
- .map((rlAction) => {
203
- const rlActionObj: {
204
- list?: string
205
- visible?: boolean | 'ignore'
206
- } = {}
207
-
208
- const listValue = rlAction.get('list')?.ifString()?.getValue()
209
- if (listValue) {
210
- // Strip REL: prefix if present (transform ServiceNow → Fluent)
211
- // Users write plain GUIDs or table.field format in Fluent code
212
- if (listValue.startsWith('REL:')) {
213
- rlActionObj.list = listValue.substring(4) // Remove 'REL:' prefix
214
- } else {
215
- rlActionObj.list = listValue
127
+ const relatedListActions = descendants.query(SYS_UI_POLICY_RL_ACTION).map((rlAction) =>
128
+ rlAction.transform(({ $ }) => ({
129
+ list: $.map((v) => {
130
+ if (v?.ifString()?.ifDefined()) {
131
+ const listVal = v.asString().getValue()
132
+ return listVal.startsWith('REL:') ? listVal.substring(4) : listVal
216
133
  }
217
- }
218
-
219
- // Convert ServiceNow string format to boolean | 'ignore'
220
- const visible = stringToBoolean(rlAction.get('visible')?.ifString()?.getValue())
221
- if (visible !== undefined && visible !== 'ignore') {
222
- rlActionObj.visible = visible
223
- }
224
-
225
- // Skip if no meaningful properties (only $id exists)
226
- if (Object.keys(rlActionObj).length === 0) {
227
- return null
228
- }
229
-
230
- return rlActionObj
231
- })
232
- .filter((rlAction) => rlAction !== null) // Remove null actions
134
+ return ''
135
+ }).def(''),
136
+ visible: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
137
+ }))
138
+ )
233
139
 
234
140
  return {
235
141
  success: true,
@@ -278,7 +184,7 @@ export const UiPolicyPlugin = Plugin.create({
278
184
  order: $.from('order').toNumber().def(100),
279
185
  setValues: $.from('set_values').def(''),
280
186
  view: $.from('view').def(''),
281
- actions: $.val(actions.length > 0 ? actions : undefined),
187
+ actions: $.val(actions).def([]),
282
188
  relatedListActions: $.val(
283
189
  relatedListActions.length > 0 ? relatedListActions : undefined
284
190
  ),
@@ -401,11 +307,10 @@ export const UiPolicyPlugin = Plugin.create({
401
307
  const hasClearedProp = action.get('cleared')?.isBoolean() || false
402
308
 
403
309
  if (!hasVisibleProp && !hasReadOnlyProp && !hasMandatoryProp && !hasClearedProp) {
404
- diagnostics.error(
310
+ diagnostics.hint(
405
311
  action,
406
- `Action at index ${i} must specify at least one of: visible, readOnly, mandatory, or cleared`
312
+ `Action at index ${i} has no effect — consider specifying at least one of: visible, readOnly, mandatory, or cleared`
407
313
  )
408
- continue
409
314
  }
410
315
 
411
316
  const actionRecord = await factory.createRecord({
@@ -475,14 +380,12 @@ export const UiPolicyPlugin = Plugin.create({
475
380
  }
476
381
  }
477
382
 
478
- // At least one property must be specified (visible or list)
479
383
  const hasVisibleProp = isValidActionValue(rlAction.get('visible'))
480
384
  if (!hasVisibleProp && !listValue) {
481
- diagnostics.error(
385
+ diagnostics.hint(
482
386
  rlAction,
483
- `Related list action at index ${i} must specify at least one of: list or visible`
387
+ `Related list action at index ${i} has no effect — consider specifying at least one of: list or visible`
484
388
  )
485
- continue
486
389
  }
487
390
 
488
391
  const rlActionRecord = await factory.createRecord({
@@ -1,6 +1,5 @@
1
1
  import { CallExpressionShape, Plugin } from '@servicenow/sdk-build-core'
2
2
  import { NowIdShape } from './now-id-plugin'
3
- import { createSdkDocEntry } from './utils'
4
3
 
5
4
  /**
6
5
  * Creates a User Preference (sys_user_preference).
@@ -17,7 +16,6 @@ import { createSdkDocEntry } from './utils'
17
16
  */
18
17
  export const UserPreferencePlugin = Plugin.create({
19
18
  name: 'UserPreferencePlugin',
20
- docs: [createSdkDocEntry('UserPreference', ['sys_user_preference'])],
21
19
  records: {
22
20
  sys_user_preference: {
23
21
  toShape(record) {