@servicenow/sdk-build-plugins 4.6.1 → 4.7.1

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 (279) hide show
  1. package/dist/acl-plugin.js +0 -3
  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 +0 -2
  10. package/dist/atf/test-plugin.js.map +1 -1
  11. package/dist/basic-syntax-plugin.js +0 -1
  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 +24 -7
  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 +0 -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 +2 -2
  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 +0 -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 +0 -2
  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 +0 -2
  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/variable-set-plugin.js +0 -2
  148. package/dist/service-catalog/variable-set-plugin.js.map +1 -1
  149. package/dist/service-portal/angular-provider-plugin.js +0 -2
  150. package/dist/service-portal/angular-provider-plugin.js.map +1 -1
  151. package/dist/service-portal/dependency-plugin.js +3 -5
  152. package/dist/service-portal/dependency-plugin.js.map +1 -1
  153. package/dist/service-portal/header-footer-plugin.js +3 -5
  154. package/dist/service-portal/header-footer-plugin.js.map +1 -1
  155. package/dist/service-portal/menu-plugin.js +0 -1
  156. package/dist/service-portal/menu-plugin.js.map +1 -1
  157. package/dist/service-portal/page-plugin.js +0 -1
  158. package/dist/service-portal/page-plugin.js.map +1 -1
  159. package/dist/service-portal/page-route-map-plugin.js +0 -1
  160. package/dist/service-portal/page-route-map-plugin.js.map +1 -1
  161. package/dist/service-portal/portal-plugin.js +0 -2
  162. package/dist/service-portal/portal-plugin.js.map +1 -1
  163. package/dist/service-portal/theme-plugin.js +0 -2
  164. package/dist/service-portal/theme-plugin.js.map +1 -1
  165. package/dist/service-portal/widget-plugin.js +3 -5
  166. package/dist/service-portal/widget-plugin.js.map +1 -1
  167. package/dist/sla-plugin.js +0 -2
  168. package/dist/sla-plugin.js.map +1 -1
  169. package/dist/static-content-plugin.js +32 -3
  170. package/dist/static-content-plugin.js.map +1 -1
  171. package/dist/table-plugin.js +102 -11
  172. package/dist/table-plugin.js.map +1 -1
  173. package/dist/ui-action-plugin.js +26 -17
  174. package/dist/ui-action-plugin.js.map +1 -1
  175. package/dist/ui-page-plugin.js +159 -17
  176. package/dist/ui-page-plugin.js.map +1 -1
  177. package/dist/ui-policy-plugin.js +0 -1
  178. package/dist/ui-policy-plugin.js.map +1 -1
  179. package/dist/user-preference-plugin.js +0 -2
  180. package/dist/user-preference-plugin.js.map +1 -1
  181. package/dist/utils.d.ts +1 -9
  182. package/dist/utils.js +0 -14
  183. package/dist/utils.js.map +1 -1
  184. package/dist/ux-list-menu-config-plugin.js +0 -2
  185. package/dist/ux-list-menu-config-plugin.js.map +1 -1
  186. package/dist/view-plugin.js +0 -1
  187. package/dist/view-plugin.js.map +1 -1
  188. package/dist/workspace-plugin.js +0 -2
  189. package/dist/workspace-plugin.js.map +1 -1
  190. package/package.json +6 -6
  191. package/src/acl-plugin.ts +1 -4
  192. package/src/applicability-plugin.ts +0 -2
  193. package/src/application-menu-plugin.ts +0 -2
  194. package/src/arrow-function-plugin.ts +0 -1
  195. package/src/atf/test-plugin.ts +0 -2
  196. package/src/basic-syntax-plugin.ts +0 -1
  197. package/src/business-rule-plugin.ts +1 -2
  198. package/src/call-expression-plugin.ts +0 -1
  199. package/src/claims-plugin.ts +0 -1
  200. package/src/client-script-plugin.ts +1 -2
  201. package/src/column-plugin.ts +29 -9
  202. package/src/cross-scope-privilege-plugin.ts +1 -2
  203. package/src/dashboard/dashboard-plugin.ts +0 -2
  204. package/src/data-plugin.ts +0 -1
  205. package/src/data-policy-plugin.ts +333 -0
  206. package/src/email-notification-plugin.ts +8 -4
  207. package/src/flow/flow-logic/flow-logic-constants.ts +6 -0
  208. package/src/flow/flow-logic/flow-logic-diagnostics.ts +236 -58
  209. package/src/flow/flow-logic/flow-logic-plugin-helpers.ts +59 -6
  210. package/src/flow/flow-logic/flow-logic-plugin.ts +368 -38
  211. package/src/flow/flow-logic/flow-logic-shapes.ts +25 -0
  212. package/src/flow/plugins/approval-rules-plugin.ts +0 -1
  213. package/src/flow/plugins/flow-action-definition-plugin.ts +940 -208
  214. package/src/flow/plugins/flow-data-pill-plugin.ts +3 -5
  215. package/src/flow/plugins/flow-definition-plugin.ts +159 -26
  216. package/src/flow/plugins/flow-diagnostics-plugin.ts +89 -3
  217. package/src/flow/plugins/flow-instance-plugin.ts +26 -12
  218. package/src/flow/plugins/flow-trigger-instance-plugin.ts +0 -1
  219. package/src/flow/plugins/inline-script-plugin.ts +0 -1
  220. package/src/flow/plugins/step-definition-plugin.ts +0 -2
  221. package/src/flow/plugins/step-instance-plugin.ts +259 -65
  222. package/src/flow/plugins/trigger-plugin.ts +0 -2
  223. package/src/flow/plugins/wfa-datapill-plugin.ts +0 -1
  224. package/src/flow/utils/datapill-transformer.ts +13 -5
  225. package/src/flow/utils/flow-constants.ts +19 -1
  226. package/src/flow/utils/flow-io-to-record.ts +29 -19
  227. package/src/flow/utils/flow-pill-utils.ts +48 -0
  228. package/src/flow/utils/flow-stage-processor.ts +831 -0
  229. package/src/flow/utils/pill-string-parser.ts +29 -47
  230. package/src/flow/utils/utils.ts +39 -35
  231. package/src/form-plugin.ts +5 -15
  232. package/src/html-import-plugin.ts +0 -1
  233. package/src/import-sets-plugin.ts +0 -2
  234. package/src/inbound-email-action-plugin.ts +1 -2
  235. package/src/index.ts +7 -1
  236. package/src/instance-scan-plugin.ts +0 -7
  237. package/src/json-plugin.ts +0 -1
  238. package/src/list-plugin.ts +6 -2
  239. package/src/now-attach-plugin.ts +0 -1
  240. package/src/now-config-plugin.ts +0 -1
  241. package/src/now-id-plugin.ts +0 -1
  242. package/src/now-include-plugin.ts +0 -1
  243. package/src/now-ref-plugin.ts +0 -1
  244. package/src/now-unresolved-plugin.ts +0 -1
  245. package/src/package-json-plugin.ts +8 -3
  246. package/src/property-plugin.ts +0 -2
  247. package/src/record-plugin.ts +3 -3
  248. package/src/repack/lint/Rules.ts +1 -1
  249. package/src/rest-api-plugin.ts +7 -6
  250. package/src/role-plugin.ts +1 -2
  251. package/src/schedule-script/scheduled-script-plugin.ts +11 -5
  252. package/src/script-action-plugin.ts +0 -2
  253. package/src/script-include-plugin.ts +0 -4
  254. package/src/server-module-plugin/index.ts +2 -3
  255. package/src/service-catalog/catalog-clientscript-plugin.ts +0 -2
  256. package/src/service-catalog/catalog-item-plugin.ts +0 -2
  257. package/src/service-catalog/catalog-ui-policy-plugin.ts +0 -2
  258. package/src/service-catalog/sc-record-producer-plugin.ts +0 -2
  259. package/src/service-catalog/variable-set-plugin.ts +0 -2
  260. package/src/service-portal/angular-provider-plugin.ts +0 -2
  261. package/src/service-portal/dependency-plugin.ts +0 -2
  262. package/src/service-portal/header-footer-plugin.ts +0 -2
  263. package/src/service-portal/menu-plugin.ts +1 -2
  264. package/src/service-portal/page-plugin.ts +1 -2
  265. package/src/service-portal/page-route-map-plugin.ts +1 -2
  266. package/src/service-portal/portal-plugin.ts +0 -2
  267. package/src/service-portal/theme-plugin.ts +0 -2
  268. package/src/service-portal/widget-plugin.ts +0 -2
  269. package/src/sla-plugin.ts +0 -2
  270. package/src/static-content-plugin.ts +37 -4
  271. package/src/table-plugin.ts +118 -16
  272. package/src/ui-action-plugin.ts +30 -17
  273. package/src/ui-page-plugin.ts +188 -20
  274. package/src/ui-policy-plugin.ts +1 -2
  275. package/src/user-preference-plugin.ts +0 -2
  276. package/src/utils.ts +0 -15
  277. package/src/ux-list-menu-config-plugin.ts +0 -2
  278. package/src/view-plugin.ts +0 -1
  279. package/src/workspace-plugin.ts +0 -2
@@ -1,4 +1,4 @@
1
- import type { FlowLogicInstanceShape, DoInParallelInstanceShape } from './flow-logic-shapes'
1
+ import type { FlowLogicInstanceShape } from './flow-logic-shapes'
2
2
  import {
3
3
  ts,
4
4
  PropertyAccessShape,
@@ -8,11 +8,19 @@ import {
8
8
  ObjectShape,
9
9
  CallExpressionShape,
10
10
  type Diagnostics,
11
+ type Shape,
11
12
  } from '@servicenow/sdk-build-core'
12
13
  import { FLOW_LOGIC } from './flow-logic-constants'
13
14
  import { getCallExpressionName } from '../../utils'
14
15
  import { findAncestorByCalleeName } from '../utils/utils'
15
- import { isDoInParallelShape, isSetFlowVariablesShape, isAssignSubflowOutputsShape } from './flow-logic-plugin-helpers'
16
+ import {
17
+ isDoInParallelShape,
18
+ isSetFlowVariablesShape,
19
+ isAppendToFlowVariablesShape,
20
+ isAssignSubflowOutputsShape,
21
+ isTryCatchShape,
22
+ } from './flow-logic-plugin-helpers'
23
+ import { ArrowFunctionShape } from '../../arrow-function-plugin'
16
24
  import { PillShape } from '../utils/data-pill-shapes'
17
25
  import { ARRAY_FLOW_DATA_TYPES, type ArrayFlowDataType } from '@servicenow/sdk-core/runtime/flow'
18
26
  import { FDInlineScriptCallShape } from '../plugins/inline-script-plugin'
@@ -30,8 +38,9 @@ const FLOW_LOGIC_VALIDATORS: { [K in FLOW_LOGIC]: FlowLogicValidator } = {
30
38
  [FLOW_LOGIC.WAIT_FOR_A_DURATION]: validateWaitForADuration,
31
39
  [FLOW_LOGIC.FOR_EACH]: validateForEach,
32
40
  [FLOW_LOGIC.DO_IN_PARALLEL]: validateDoInParallel,
33
- [FLOW_LOGIC.TRY_CATCH]: () => undefined,
41
+ [FLOW_LOGIC.TRY_CATCH]: validateTryCatch,
34
42
  [FLOW_LOGIC.SET_FLOW_VARIABLES]: validateSetFlowVariables,
43
+ [FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES]: validateAppendToFlowVariables,
35
44
  [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: validateAssignSubflowOutputs,
36
45
  }
37
46
 
@@ -137,8 +146,8 @@ function findParentFlowLogic(node: ts.Node | undefined): ts.CallExpression | und
137
146
 
138
147
  const callName = getCallExpressionName(current)
139
148
 
140
- if ([FLOW_LOGIC.IF, FLOW_LOGIC.ELSEIF, FLOW_LOGIC.ELSE, 'TryCatch', 'MakeADecision'].includes(callName)) {
141
- if (callName === 'TryCatch') {
149
+ if ([FLOW_LOGIC.IF, FLOW_LOGIC.ELSEIF, FLOW_LOGIC.ELSE, FLOW_LOGIC.TRY_CATCH, 'MakeADecision'].includes(callName)) {
150
+ if (callName === FLOW_LOGIC.TRY_CATCH) {
142
151
  // For TryCatch, GoBackTo should only be in the catch block, not in try block
143
152
  if (!isInCatchBlock(node)) {
144
153
  return undefined // Invalid placement - not in catch block
@@ -311,13 +320,36 @@ function validateForEach(expr: FlowLogicInstanceShape): string | undefined {
311
320
  return undefined
312
321
  }
313
322
 
323
+ // TypeScript enforces argument count, $id, and `() => void` shape of try/catch.
324
+ // The plugin's transform logic requires arrow functions specifically, which TS does not distinguish from other
325
+ // function forms — so check that here.
326
+ function validateTryCatch(expr: FlowLogicInstanceShape): string | undefined {
327
+ if (!isTryCatchShape(expr)) {
328
+ return undefined
329
+ }
330
+
331
+ const handlers = expr.getArgument(1)?.if(ObjectShape)
332
+ const tryHandler = handlers?.get('try')
333
+ const catchHandler = handlers?.get('catch')
334
+
335
+ if (!tryHandler?.if(ArrowFunctionShape)) {
336
+ return "TryCatch: 'try' handler must be an arrow function"
337
+ }
338
+
339
+ if (!catchHandler?.if(ArrowFunctionShape)) {
340
+ return "TryCatch: 'catch' handler must be an arrow function"
341
+ }
342
+
343
+ return undefined
344
+ }
345
+
314
346
  function validateDoInParallel(expr: FlowLogicInstanceShape): string | undefined {
315
347
  if (isDoInParallelShape(expr)) {
316
- const doInParallelExpr = expr as DoInParallelInstanceShape
317
- const originalNode = doInParallelExpr.getOriginalNode()
348
+ // Check for nested `DoInParallel`. `findAncestorByCalleeName` returns the node itself when it matches, so
349
+ // start the search from the parent call expression. Otherwise, every `DoInParallel` would flag itself.
350
+ const parentCall = expr.getOriginalNode()?.getFirstAncestorByKind(ts.SyntaxKind.CallExpression)
351
+ const parentDoInParallel = findAncestorByCalleeName(parentCall, FLOW_LOGIC.DO_IN_PARALLEL)
318
352
 
319
- // Check for nested DoInParallel
320
- const parentDoInParallel = findAncestorByCalleeName(originalNode, FLOW_LOGIC.DO_IN_PARALLEL)
321
353
  if (parentDoInParallel) {
322
354
  return 'Nested DoInParallel calls are not allowed. DoInParallel cannot be used within another DoInParallel block.'
323
355
  }
@@ -327,83 +359,69 @@ function validateDoInParallel(expr: FlowLogicInstanceShape): string | undefined
327
359
  }
328
360
 
329
361
  /**
330
- * Validates SetFlowVariables flow logic instance.
331
- * Performs comprehensive validation of arguments, types, and structure.
362
+ * Shared validator for "variable assignment"-style flow logic shapes (SetFlowVariables and AppendToFlowVariables).
363
+ * Both shapes share the (config, schema, values) argument layout and the same value-type rules; only the type guard,
364
+ * the values' accessor, and the error-message prefix differ. Routing both through one helper prevents the two
365
+ * implementations from drifting independently and ensures any future validation change applies to both call sites at
366
+ * once.
332
367
  *
333
- * **Validations performed:**
334
- * 1. Argument count: Must have exactly 3 arguments
335
- * 2. Config object: Must contain required $id property
336
- * 3. Schema argument: Must be params.flowVariables (schema reference)
337
- * 4. Values object: Must be non-empty
338
- * 5. Variable names: Cannot be empty or whitespace
339
- * 6. Variable values: Must be valid types (string, number, boolean, or datapill)
368
+ * Validations performed:
369
+ * 1. Argument count must be exactly 3 (config, schema, values).
370
+ * 2. Config must expose a valid $id (`expr.getSysId()` must not throw).
371
+ * 3. Values argument must be a defined, non-empty object.
372
+ * 4. Every key must be a non-empty string.
373
+ * 5. Every value must be a string / number / boolean / object / array / datapill / inline script.
340
374
  *
341
- * **Valid value types:**
342
- * - String literals: "hello"
343
- * - Numbers: 42, 3.14
344
- * - Booleans: true, false
345
- * - Objects: { a: "value", b: 42 } (ObjectShape - for FlowObject types)
346
- * - Arrays: [1, 2, 3] (ArrayShape - for FlowArray types)
347
- * - Datapills: params.trigger.name, params.complexObject (PropertyAccessShape)
348
- * - Template expressions: `Hello ${params.trigger.name}` (TemplateExpressionShape)
349
- *
350
- * @param expr - The SetFlowVariables flow logic instance shape
351
- * @returns Error message if validation fails, undefined if valid
352
- *
353
- * @example
354
- * // Valid usage
355
- * wfa.flowLogic.setFlowVariables(
356
- * { $id: Now.ID['...'] },
357
- * params.flowVariables, // ← Schema reference (2nd argument)
358
- * { user: params.trigger.name, count: 42, active: true }
359
- * )
375
+ * @param expr - The flow logic instance shape to validate.
376
+ * @param name - API name used as the error-message prefix (e.g., 'SetFlowVariables').
377
+ * @param isShape - Type-guard distinguishing the specific assignment shape.
378
+ * @param getValues - Accessor returning the values ObjectShape from the narrowed shape.
379
+ * @returns Error message if validation fails, undefined otherwise.
360
380
  */
361
- function validateSetFlowVariables(expr: FlowLogicInstanceShape): string | undefined {
362
- if (!isSetFlowVariablesShape(expr)) {
381
+ function validateVariableAssignmentShape<S extends FlowLogicInstanceShape>(
382
+ expr: FlowLogicInstanceShape,
383
+ name: string,
384
+ isShape: (e: FlowLogicInstanceShape) => e is S,
385
+ getValues: (shape: S) => ObjectShape | undefined
386
+ ): string | undefined {
387
+ if (!isShape(expr)) {
363
388
  return undefined
364
389
  }
365
390
 
366
- // Validate argument count
367
391
  const args = expr.getArguments()
368
392
  if (args.length !== 3) {
369
- return `SetFlowVariables requires exactly 3 arguments (config, schema, values), but received ${args.length}`
393
+ return `${name} requires exactly 3 arguments (config, schema, values), but received ${args.length}`
370
394
  }
371
395
 
372
- // Validate config argument
373
396
  try {
374
397
  expr.getSysId()
375
- } catch (error) {
376
- return 'SetFlowVariables: First argument (config) must be an object with valid $id property'
398
+ } catch {
399
+ return `${name}: First argument (config) must be an object with valid $id property`
377
400
  }
378
401
 
379
- // Validate values argument (third argument)
380
- const valuesArg = expr.getVariablesToSet()
402
+ const valuesArg = getValues(expr)
381
403
  if (!valuesArg || valuesArg.isUndefined()) {
382
- return 'SetFlowVariables: Third argument (values) is required'
404
+ return `${name}: Third argument (values) is required`
383
405
  }
384
406
 
385
407
  if (!valuesArg.isObject()) {
386
- return 'SetFlowVariables: Third argument (values) must be an object'
408
+ return `${name}: Third argument (values) must be an object`
387
409
  }
388
410
 
389
- // Validate that values object is not empty
390
411
  const entries = Array.from(valuesArg.entries({ resolve: false }))
391
412
  if (entries.length === 0) {
392
- return 'SetFlowVariables: Values object cannot be empty - at least one variable must be set'
413
+ return `${name}: Values object cannot be empty - at least one variable is required`
393
414
  }
394
415
 
395
- // Validate each value in the object
396
416
  for (const [key, valueShape] of entries) {
397
417
  if (!key || key.trim() === '') {
398
- return 'SetFlowVariables: Variable names cannot be empty'
418
+ return `${name}: Variable names cannot be empty`
399
419
  }
400
420
 
401
- // Check if value is valid (string, datapill, template expression, or inline script)
402
421
  if (valueShape.isUndefined()) {
403
- return `SetFlowVariables: Variable '${key}' has undefined value`
422
+ return `${name}: Variable '${key}' has undefined value`
404
423
  }
405
424
 
406
- // Values can be: strings, numbers, booleans, objects, arrays, datapills, template expressions, or inline scripts
407
425
  const isDatapill = valueShape instanceof PropertyAccessShape || valueShape instanceof TemplateExpressionShape
408
426
  const isInlineScript = valueShape instanceof FDInlineScriptCallShape
409
427
  const isIdentifier = valueShape instanceof IdentifierShape
@@ -417,17 +435,177 @@ function validateSetFlowVariables(expr: FlowLogicInstanceShape): string | undefi
417
435
  isInlineScript
418
436
 
419
437
  if (!isValidType) {
420
- // Provide specific error message for unsupported types
421
438
  if (isIdentifier) {
422
- return `SetFlowVariables: Variable '${key}' cannot use local variable references. Use direct values (literals) or datapills (e.g., params.someField) instead`
439
+ return `${name}: Variable '${key}' cannot use local variable references. Use direct values (literals) or datapills (e.g., params.someField) instead`
423
440
  }
424
- return `SetFlowVariables: Variable '${key}' has invalid value type. Expected string, number, boolean, object, array, or datapill expression`
441
+
442
+ return `${name}: Variable '${key}' has invalid value type. Expected string, number, boolean, object, array, or datapill expression`
443
+ }
444
+ }
445
+
446
+ return undefined
447
+ }
448
+
449
+ function validateSetFlowVariables(expr: FlowLogicInstanceShape): string | undefined {
450
+ return validateVariableAssignmentShape(expr, 'SetFlowVariables', isSetFlowVariablesShape, (s) =>
451
+ s.getVariablesToSet()
452
+ )
453
+ }
454
+
455
+ /**
456
+ * Validates AppendToFlowVariables flow logic instance.
457
+ *
458
+ * The "target must be array-typed" constraint is enforced at compile time by the API type signature
459
+ * (`KV[K] extends Array<infer E> ? ... : never`). The platform only supports appending to Array.Object flow variables —
460
+ * other array element types (string / integer / boolean / datetime) are runtime no-ops, so this validator emits an
461
+ * error for any target whose flow variable is not declared as `FlowArray({ elementType: FlowObject(...) })`. The
462
+ * remaining argument / structural checks are shared with SetFlowVariables via `validateVariableAssignmentShape`.
463
+ *
464
+ * Additionally, when an array literal is provided as the value for an Array.Object target, every element must itself
465
+ * be an object literal or a datapill expression — primitive literals are not valid array.object elements and the TS
466
+ * `unknown[]` arm of the value union lets them slip past the compile-time check.
467
+ *
468
+ * @param expr - The AppendToFlowVariables flow logic instance shape
469
+ * @returns Error message if validation fails, undefined if valid
470
+ */
471
+ function validateAppendToFlowVariables(expr: FlowLogicInstanceShape): string | undefined {
472
+ const shapeError = validateVariableAssignmentShape(
473
+ expr,
474
+ 'AppendToFlowVariables',
475
+ isAppendToFlowVariablesShape,
476
+ (s) => s.getValuesToAppend()
477
+ )
478
+ if (shapeError) {
479
+ return shapeError
480
+ }
481
+
482
+ if (!isAppendToFlowVariablesShape(expr)) {
483
+ return undefined
484
+ }
485
+
486
+ const parentFlow = findAncestorByCalleeName(expr.getOriginalNode(), 'Flow', 'Subflow')
487
+ if (!parentFlow) {
488
+ // Placement is already validated by the umbrella check; nothing further to assert here.
489
+ return undefined
490
+ }
491
+
492
+ const flowVariableInitializers = getFlowVariableInitializers(parentFlow)
493
+ const valuesArg = expr.getValuesToAppend()
494
+ for (const [key, valueShape] of valuesArg.entries({ resolve: false })) {
495
+ const initializer = flowVariableInitializers.get(key)
496
+ // If the variable isn't declared in flowVariables, the TS signature has already rejected the key at compile
497
+ // time — skip rather than double-reporting.
498
+ if (!initializer) {
499
+ continue
500
+ }
501
+ if (!isArrayObjectFlowVariable(initializer)) {
502
+ return `AppendToFlowVariables: Variable '${key}' is not an Array.Object flow variable. AppendToFlowVariables only supports Array.Object targets — declare the variable as FlowArray({ elementType: FlowObject(...) }).`
503
+ }
504
+ const elementError = validateArrayObjectElements(key, valueShape)
505
+ if (elementError) {
506
+ return elementError
507
+ }
508
+ }
509
+
510
+ return undefined
511
+ }
512
+
513
+ /**
514
+ * For an Array.Object target, every element of an array-literal value must be an object literal or a datapill
515
+ * expression. A primitive literal (string / number / boolean) inside the array is silently accepted by TypeScript
516
+ * (via the `unknown[]` arm of the value union) but rejected by the platform at runtime, so we flag it here.
517
+ *
518
+ * Non-array values (single object literal, whole-field template string, whole-field datapill) are accepted as-is —
519
+ * those arms are already covered by the shared `validateVariableAssignmentShape` checks and by the platform's own
520
+ * runtime handling.
521
+ */
522
+ function validateArrayObjectElements(key: string, valueShape: Shape): string | undefined {
523
+ if (!valueShape.isArray()) {
524
+ return undefined
525
+ }
526
+
527
+ const elements = valueShape.asArray().getElements(false)
528
+ for (const [i, element] of elements.entries()) {
529
+ if (isArrayObjectElement(element)) {
530
+ continue
425
531
  }
532
+
533
+ return `AppendToFlowVariables: Variable '${key}' has an invalid element at index ${i}. Array.Object variables only accept object literals or datapill expressions as elements — primitive values are not allowed.`
426
534
  }
427
535
 
428
536
  return undefined
429
537
  }
430
538
 
539
+ function isArrayObjectElement(element: Shape): boolean {
540
+ if (element instanceof ObjectShape) {
541
+ return true
542
+ }
543
+ if (
544
+ element instanceof PropertyAccessShape ||
545
+ element instanceof TemplateExpressionShape ||
546
+ element instanceof PillShape ||
547
+ element instanceof FDInlineScriptCallShape
548
+ ) {
549
+ return true
550
+ }
551
+
552
+ // `wfa.dataPill(...)` calls are unwrapped to PillShape by the DataPillPlugin in normal builds. However, raw
553
+ // CallExpressionShape can still arrive here for fixtures that haven't been subclassed yet — accept any call
554
+ // expression whose callee resolves to wfa.dataPill so the order of plugin subclassing does not matter.
555
+ return element instanceof CallExpressionShape && element.getCallee() === 'wfa.dataPill'
556
+ }
557
+
558
+ /**
559
+ * Reads a Flow / Subflow's `flowVariables` config property and returns a map from variable name to its initializer
560
+ * expression. Returns an empty map when `flowVariables` is missing or shaped unexpectedly — those cases are handled by
561
+ * other validators (or by the TypeScript type system) and should not be re-flagged here.
562
+ */
563
+ function getFlowVariableInitializers(flowOrSubflow: ts.CallExpression): Map<string, ts.Expression> {
564
+ const result = new Map<string, ts.Expression>()
565
+ const configArg = flowOrSubflow.getArguments()[0]
566
+ if (!configArg || !ts.Node.isObjectLiteralExpression(configArg)) {
567
+ return result
568
+ }
569
+ const flowVarsProp = configArg.getProperty('flowVariables')?.asKind(ts.SyntaxKind.PropertyAssignment)
570
+ const flowVarsObject = flowVarsProp?.getInitializer()?.asKind(ts.SyntaxKind.ObjectLiteralExpression)
571
+ if (!flowVarsObject) {
572
+ return result
573
+ }
574
+ for (const prop of flowVarsObject.getProperties()) {
575
+ const propertyAssignment = prop.asKind(ts.SyntaxKind.PropertyAssignment)
576
+ const initializer = propertyAssignment?.getInitializer()
577
+ if (!propertyAssignment || !initializer) {
578
+ continue
579
+ }
580
+ result.set(getPropertyAssignmentName(propertyAssignment), initializer)
581
+ }
582
+ return result
583
+ }
584
+
585
+ function getPropertyAssignmentName(node: ts.PropertyAssignment): string {
586
+ const nameNode = node.getNameNode()
587
+ return ts.Node.isStringLiteral(nameNode) || ts.Node.isNoSubstitutionTemplateLiteral(nameNode)
588
+ ? nameNode.getLiteralValue()
589
+ : nameNode.getText()
590
+ }
591
+
592
+ /**
593
+ * Returns true when the given flow-variable initializer expression is a `FlowArray({ elementType: FlowObject(...) })`
594
+ * call — the only flow-variable shape that AppendToFlowVariables can target at runtime.
595
+ */
596
+ function isArrayObjectFlowVariable(initializer: ts.Expression): boolean {
597
+ if (!ts.Node.isCallExpression(initializer) || getCallExpressionName(initializer) !== 'FlowArray') {
598
+ return false
599
+ }
600
+ const flowArrayConfig = initializer.getArguments()[0]?.asKind(ts.SyntaxKind.ObjectLiteralExpression)
601
+ const elementTypeProp = flowArrayConfig?.getProperty('elementType')?.asKind(ts.SyntaxKind.PropertyAssignment)
602
+ const elementTypeInit = elementTypeProp?.getInitializer()
603
+ if (!elementTypeInit || !ts.Node.isCallExpression(elementTypeInit)) {
604
+ return false
605
+ }
606
+ return getCallExpressionName(elementTypeInit) === 'FlowObject'
607
+ }
608
+
431
609
  /**
432
610
  * Validates AssignSubflowOutputs flow logic instance.
433
611
  * Performs comprehensive validation of arguments, types, structure, and placement.
@@ -7,6 +7,7 @@ import type {
7
7
  TryCatchInstanceShape,
8
8
  DoInParallelInstanceShape,
9
9
  SetFlowVariablesInstanceShape,
10
+ AppendToFlowVariablesInstanceShape,
10
11
  AssignSubflowOutputsInstanceShape,
11
12
  } from './flow-logic-shapes'
12
13
  import {
@@ -33,7 +34,7 @@ import {
33
34
  DURATION_TYPE_PROPS,
34
35
  WAIT_FOR_A_DURATION_INPUT_DESCRIPTORS,
35
36
  } from './flow-logic-constants'
36
- import { resolveDataPillShape, sysIdToUuid } from '../utils/utils'
37
+ import { getOrderSuffix, resolveDataPillShape, sysIdToUuid } from '../utils/utils'
37
38
  import { ArrowFunctionShape } from '../../arrow-function-plugin'
38
39
  import { NowIdShape } from '../../now-id-plugin'
39
40
  import { validateFlowLogic, validateSibling } from './flow-logic-diagnostics'
@@ -222,6 +223,9 @@ export namespace FlowLogicValueProcessor {
222
223
  [FLOW_LOGIC.SET_FLOW_VARIABLES]: (data) => {
223
224
  return prepareSetFlowVariables(data.asObject(), '')
224
225
  },
226
+ [FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES]: (data) => {
227
+ return prepareSetFlowVariables(data.asObject(), '')
228
+ },
225
229
  [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: (data) => {
226
230
  return prepareAssignSubflowOutputs(data.asObject())
227
231
  },
@@ -266,7 +270,7 @@ export namespace FlowLogicValueProcessor {
266
270
  return {
267
271
  outputsToAssign: logicType === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS ? preparedData : [],
268
272
  variables:
269
- logicType === FLOW_LOGIC.SET_FLOW_VARIABLES
273
+ logicType === FLOW_LOGIC.SET_FLOW_VARIABLES || logicType === FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES
270
274
  ? prepareSetFlowVariables(data.asObject(), 'variables')
271
275
  : [],
272
276
  decisionTableInputs: [],
@@ -655,7 +659,12 @@ export namespace FlowLogicValueProcessor {
655
659
  [FLOW_LOGIC.FOR_EACH]: parseForEach,
656
660
  [FLOW_LOGIC.DO_IN_PARALLEL]: () => ({}),
657
661
  [FLOW_LOGIC.TRY_CATCH]: () => ({}),
662
+ // Unreachable: parse() short-circuits for SET_FLOW_VARIABLES, APPEND_TO_FLOW_VARIABLES, and
663
+ // ASSIGN_SUBFLOW_OUTPUTS before this lookup runs — their payloads live in `variables` / `outputsToAssign`, not
664
+ // `inputs`, so they take dedicated branches that delegate to `parseEntriesFromArray`. The entries below exist
665
+ // only to satisfy the `{ [K in FLOW_LOGIC]: ValueParser }` exhaustiveness constraint.
658
666
  [FLOW_LOGIC.SET_FLOW_VARIABLES]: () => ({}),
667
+ [FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES]: () => ({}),
659
668
  [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: () => ({}),
660
669
  }
661
670
 
@@ -688,8 +697,9 @@ export namespace FlowLogicValueProcessor {
688
697
  ): FlowLogicParseResult {
689
698
  const valuesJson = parseGzippedJson(gZippedString)
690
699
 
691
- // Special handling for SET_FLOW_VARIABLES - data is in variables array
692
- if (logicType === FLOW_LOGIC.SET_FLOW_VARIABLES) {
700
+ // Special handling for SET_FLOW_VARIABLES and APPEND_TO_FLOW_VARIABLES - data is in the variable array.
701
+ // Both flow logics share the same wire format; only the platform-side semantics differ (overwrite vs. append).
702
+ if (logicType === FLOW_LOGIC.SET_FLOW_VARIABLES || logicType === FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES) {
693
703
  if (valuesJson?.variables?.length) {
694
704
  return parseEntriesFromArray(valuesJson.variables, typeMap, labelCacheMap)
695
705
  }
@@ -1642,6 +1652,10 @@ export function isSetFlowVariablesShape(expr: FlowLogicInstanceShape): expr is S
1642
1652
  return expr.getCallee() === FLOW_LOGIC.SET_FLOW_VARIABLES
1643
1653
  }
1644
1654
 
1655
+ export function isAppendToFlowVariablesShape(expr: FlowLogicInstanceShape): expr is AppendToFlowVariablesInstanceShape {
1656
+ return expr.getCallee() === FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES
1657
+ }
1658
+
1645
1659
  export function isAssignSubflowOutputsShape(expr: FlowLogicInstanceShape): expr is AssignSubflowOutputsInstanceShape {
1646
1660
  return expr.getCallee() === FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS
1647
1661
  }
@@ -1905,6 +1919,11 @@ async function extractDataFromShape(
1905
1919
  return await resolveConfigDataPills(variablesToSet, transform, diagnostics)
1906
1920
  }
1907
1921
 
1922
+ if (isAppendToFlowVariablesShape(expr)) {
1923
+ const valuesToAppend = expr.getValuesToAppend()
1924
+ return await resolveConfigDataPills(valuesToAppend, transform, diagnostics)
1925
+ }
1926
+
1908
1927
  if (isAssignSubflowOutputsShape(expr)) {
1909
1928
  const assignedSubflowOutputs = expr.getAssignedSubflowOutputs()
1910
1929
  return await resolveConfigDataPills(assignedSubflowOutputs, transform, diagnostics)
@@ -1969,6 +1988,12 @@ export async function getFlowLogicInstanceRecord(
1969
1988
  })
1970
1989
  }
1971
1990
 
1991
+ if (isAppendToFlowVariablesShape(expr)) {
1992
+ instanceRecord = instanceRecord.merge({
1993
+ flow_variables_assigned: expr.getValuesToAppendAsString(),
1994
+ })
1995
+ }
1996
+
1972
1997
  instanceRecord = instanceRecord.merge({
1973
1998
  values: values,
1974
1999
  })
@@ -2084,6 +2109,7 @@ const FLOW_LOGIC_ARGUMENT_BUILDERS: { [K in FLOW_LOGIC]: FlowLogicArgumentBuilde
2084
2109
  [FLOW_LOGIC.DO_IN_PARALLEL]: buildBasicArguments,
2085
2110
  [FLOW_LOGIC.TRY_CATCH]: buildBasicArguments,
2086
2111
  [FLOW_LOGIC.SET_FLOW_VARIABLES]: buildSetFlowVariablesArguments,
2112
+ [FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES]: buildAppendToFlowVariablesArguments,
2087
2113
  [FLOW_LOGIC.ASSIGN_SUBFLOW_OUTPUTS]: buildAssignSubflowOutputsArguments,
2088
2114
  }
2089
2115
 
@@ -2226,6 +2252,31 @@ function buildSetFlowVariablesArguments(
2226
2252
  )
2227
2253
  }
2228
2254
 
2255
+ /**
2256
+ * Builds arguments for AppendToFlowVariables when converting XML to Fluent.
2257
+ * Mirrors SetFlowVariables — both target the flow's `sys_hub_flow_variable` schema and use the `{{flowVariables}}`
2258
+ * schema reference. The platform-side semantics differ (append vs. overwrite), but the wire format and argument shape
2259
+ * are identical.
2260
+ *
2261
+ * @param record - ServiceNow sys_hub_flow_logic_instance_v2 record
2262
+ * @param _logicBodyShapes - Unused (required by FlowLogicArgumentBuilder interface)
2263
+ * @param database - Database to query for the flow variable schemas
2264
+ * @returns Array of three Shape arguments for AppendToFlowVariables call
2265
+ */
2266
+ function buildAppendToFlowVariablesArguments(
2267
+ record: Record,
2268
+ _logicBodyShapes: Shape[],
2269
+ database: Database
2270
+ ): Shape<unknown>[] {
2271
+ return buildVariableOrOutputArguments(
2272
+ record,
2273
+ database,
2274
+ 'sys_hub_flow_variable',
2275
+ FLOW_LOGIC.APPEND_TO_FLOW_VARIABLES,
2276
+ '{{flowVariables}}'
2277
+ )
2278
+ }
2279
+
2229
2280
  /**
2230
2281
  * Builds arguments for AssignSubflowOutputs when converting XML to Fluent.
2231
2282
  * Creates the three arguments required by the AssignSubflowOutputs API:
@@ -2431,11 +2482,13 @@ function extractForEachParameterName(record: Record): string {
2431
2482
  }
2432
2483
 
2433
2484
  /**
2434
- * Gets the fallback parameter name based on order or default.
2485
+ * Gets the fallback parameter name based on order or default. A ForEach inside
2486
+ * a DoInParallel branch carries a composite order like `19➛20`; strip the
2487
+ * separator so the generated identifier is valid JS.
2435
2488
  */
2436
2489
  function getFallbackParameterName(record: Record): string {
2437
2490
  const order = record.get('order')?.getValue() as string | undefined
2438
- return order ? `item_${order}` : 'item'
2491
+ return order ? `item_${getOrderSuffix(order)}` : 'item'
2439
2492
  }
2440
2493
 
2441
2494
  function buildForEachArguments(record: Record, logicBodyShapes: Shape[]): Shape<unknown>[] {