@servicenow/sdk-build-plugins 4.7.2 → 4.8.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 (114) hide show
  1. package/dist/alias/alias-plugin.d.ts +2 -0
  2. package/dist/alias/alias-plugin.js +183 -0
  3. package/dist/alias/alias-plugin.js.map +1 -0
  4. package/dist/alias/alias-template-plugin.d.ts +2 -0
  5. package/dist/alias/alias-template-plugin.js +232 -0
  6. package/dist/alias/alias-template-plugin.js.map +1 -0
  7. package/dist/alias/index.d.ts +3 -0
  8. package/dist/alias/index.js +20 -0
  9. package/dist/alias/index.js.map +1 -0
  10. package/dist/alias/retry-policy-plugin.d.ts +2 -0
  11. package/dist/alias/retry-policy-plugin.js +119 -0
  12. package/dist/alias/retry-policy-plugin.js.map +1 -0
  13. package/dist/arrow-function-plugin.d.ts +1 -0
  14. package/dist/arrow-function-plugin.js +60 -21
  15. package/dist/arrow-function-plugin.js.map +1 -1
  16. package/dist/atf/test-plugin.js +1 -1
  17. package/dist/atf/test-plugin.js.map +1 -1
  18. package/dist/basic-syntax-plugin.js +7 -7
  19. package/dist/basic-syntax-plugin.js.map +1 -1
  20. package/dist/column/index.d.ts +2 -0
  21. package/dist/column/index.js +13 -0
  22. package/dist/column/index.js.map +1 -0
  23. package/dist/dashboard/dashboard-plugin.js +4 -0
  24. package/dist/dashboard/dashboard-plugin.js.map +1 -1
  25. package/dist/data-lookup-plugin.d.ts +2 -0
  26. package/dist/data-lookup-plugin.js +159 -0
  27. package/dist/data-lookup-plugin.js.map +1 -0
  28. package/dist/flow/flow-logic/flow-logic-diagnostics.js +2 -1
  29. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  30. package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
  31. package/dist/flow/plugins/flow-action-definition-plugin.js +81 -16
  32. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  33. package/dist/flow/plugins/flow-definition-plugin.js +70 -7
  34. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  35. package/dist/flow/plugins/flow-instance-plugin.d.ts +35 -1
  36. package/dist/flow/plugins/flow-instance-plugin.js +241 -7
  37. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  38. package/dist/flow/plugins/step-instance-plugin.js +61 -1
  39. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  40. package/dist/flow/post-install.d.ts +2 -1
  41. package/dist/flow/post-install.js +31 -4
  42. package/dist/flow/post-install.js.map +1 -1
  43. package/dist/flow/utils/complex-object-resolver.js +4 -2
  44. package/dist/flow/utils/complex-object-resolver.js.map +1 -1
  45. package/dist/flow/utils/datapill-transformer.d.ts +5 -72
  46. package/dist/flow/utils/datapill-transformer.js +199 -28
  47. package/dist/flow/utils/datapill-transformer.js.map +1 -1
  48. package/dist/flow/utils/flow-constants.d.ts +7 -0
  49. package/dist/flow/utils/flow-constants.js +6 -1
  50. package/dist/flow/utils/flow-constants.js.map +1 -1
  51. package/dist/flow/utils/flow-io-to-record.js +24 -15
  52. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  53. package/dist/flow/utils/flow-shapes.d.ts +8 -2
  54. package/dist/flow/utils/flow-shapes.js +19 -0
  55. package/dist/flow/utils/flow-shapes.js.map +1 -1
  56. package/dist/flow/utils/flow-variable-processor.d.ts +6 -6
  57. package/dist/flow/utils/flow-variable-processor.js +8 -8
  58. package/dist/flow/utils/flow-variable-processor.js.map +1 -1
  59. package/dist/form-plugin.js +35 -24
  60. package/dist/form-plugin.js.map +1 -1
  61. package/dist/index.d.ts +5 -1
  62. package/dist/index.js +6 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/now-attach-plugin.d.ts +1 -1
  65. package/dist/now-config-plugin.js +2 -1
  66. package/dist/now-config-plugin.js.map +1 -1
  67. package/dist/now-delete-plugin.d.ts +2 -0
  68. package/dist/now-delete-plugin.js +64 -0
  69. package/dist/now-delete-plugin.js.map +1 -0
  70. package/dist/record-plugin.d.ts +10 -0
  71. package/dist/record-plugin.js +15 -1
  72. package/dist/record-plugin.js.map +1 -1
  73. package/dist/repack/lint/Rules.js +17 -7
  74. package/dist/repack/lint/Rules.js.map +1 -1
  75. package/dist/rest-message-plugin.d.ts +2 -0
  76. package/dist/rest-message-plugin.js +331 -0
  77. package/dist/rest-message-plugin.js.map +1 -0
  78. package/dist/script-include-plugin.js +1 -1
  79. package/dist/script-include-plugin.js.map +1 -1
  80. package/dist/server-module-plugin/sbom-builder.js +17 -7
  81. package/dist/server-module-plugin/sbom-builder.js.map +1 -1
  82. package/dist/static-content-plugin.js +17 -7
  83. package/dist/static-content-plugin.js.map +1 -1
  84. package/package.json +7 -6
  85. package/src/alias/alias-plugin.ts +221 -0
  86. package/src/alias/alias-template-plugin.ts +271 -0
  87. package/src/alias/index.ts +3 -0
  88. package/src/alias/retry-policy-plugin.ts +138 -0
  89. package/src/arrow-function-plugin.ts +67 -23
  90. package/src/atf/test-plugin.ts +1 -1
  91. package/src/basic-syntax-plugin.ts +7 -7
  92. package/src/column/index.ts +7 -0
  93. package/src/dashboard/dashboard-plugin.ts +4 -0
  94. package/src/data-lookup-plugin.ts +191 -0
  95. package/src/flow/flow-logic/flow-logic-diagnostics.ts +2 -1
  96. package/src/flow/flow-logic/flow-logic-plugin.ts +0 -1
  97. package/src/flow/plugins/flow-action-definition-plugin.ts +92 -25
  98. package/src/flow/plugins/flow-definition-plugin.ts +114 -8
  99. package/src/flow/plugins/flow-instance-plugin.ts +264 -7
  100. package/src/flow/plugins/step-instance-plugin.ts +74 -2
  101. package/src/flow/post-install.ts +36 -5
  102. package/src/flow/utils/complex-object-resolver.ts +4 -2
  103. package/src/flow/utils/datapill-transformer.ts +248 -36
  104. package/src/flow/utils/flow-constants.ts +8 -0
  105. package/src/flow/utils/flow-io-to-record.ts +28 -14
  106. package/src/flow/utils/flow-shapes.ts +19 -0
  107. package/src/flow/utils/flow-variable-processor.ts +21 -10
  108. package/src/form-plugin.ts +47 -26
  109. package/src/index.ts +5 -1
  110. package/src/now-config-plugin.ts +2 -1
  111. package/src/now-delete-plugin.ts +82 -0
  112. package/src/record-plugin.ts +17 -2
  113. package/src/rest-message-plugin.ts +391 -0
  114. package/src/script-include-plugin.ts +4 -1
@@ -45,6 +45,7 @@ import {
45
45
  ActionSubflowInstanceShape,
46
46
  ApprovalDueDateShape,
47
47
  ApprovalRulesShape,
48
+ getRefSysId,
48
49
  InlineScriptShape,
49
50
  } from '../utils/flow-shapes'
50
51
  import { FDInlineScriptCallShape } from './inline-script-plugin'
@@ -68,6 +69,7 @@ import {
68
69
  UNSUPPORTED_DATA_TYPES,
69
70
  UTC_TIMEZONE_VALUE,
70
71
  ELEMENT_MAPPING_FIELD_ALIASES,
72
+ ELEMENT_MAPPING_FIELD_ALIASES_REVERSE,
71
73
  } from '../utils/flow-constants'
72
74
 
73
75
  import type { ApprovalDueDateType, ApprovalRulesType } from '@servicenow/sdk-core/runtime/flow'
@@ -141,6 +143,119 @@ function parseComplexObjectValues(obj: unknown): unknown {
141
143
  return parseValue(obj)
142
144
  }
143
145
 
146
+ /**
147
+ * Returns the complex-object payload type ('object' | 'array.object') when the given value is a
148
+ * serialized FD complex-object JSON string (has both `complexObjectSchema` and `complexObject`).
149
+ * Returns `undefined` for any other value.
150
+ */
151
+ export function getComplexObjectPayloadType(value: unknown): 'object' | 'array.object' | undefined {
152
+ if (typeof value !== 'string') {
153
+ return undefined
154
+ }
155
+ try {
156
+ const parsed = JSON.parse(value)
157
+ if (parsed && typeof parsed === 'object' && 'complexObjectSchema' in parsed && 'complexObject' in parsed) {
158
+ const co = (parsed as { complexObject?: unknown }).complexObject
159
+ return co && typeof co === 'object' && '$COCollectionField' in co ? 'array.object' : 'object'
160
+ }
161
+ } catch {
162
+ // not JSON / not a complex-object payload
163
+ }
164
+ return undefined
165
+ }
166
+
167
+ /**
168
+ * Decodes a serialized FD complex-object payload (FlowObject or FlowArray) into a plain
169
+ * JavaScript object/array by extracting the `$cv.$v` leaf values from `complexObject`.
170
+ *
171
+ * Returns `undefined` if the string cannot be decoded (malformed, empty complexObject, etc.).
172
+ * Falls back gracefully so callers can keep the verbatim string for spoke/snapshot references.
173
+ *
174
+ * Exported only for unit testing.
175
+ */
176
+ type PlainObj = { [key: string]: unknown }
177
+
178
+ export function decodeComplexObjectPayload(jsonString: string): PlainObj | PlainObj[] | unknown[] | undefined {
179
+ try {
180
+ const parsed = JSON.parse(jsonString)
181
+ if (!parsed || typeof parsed !== 'object' || !('complexObject' in parsed)) {
182
+ return undefined
183
+ }
184
+ return decodeComplexObjectNode(parsed.complexObject as PlainObj)
185
+ } catch {
186
+ return undefined
187
+ }
188
+ }
189
+
190
+ function decodeComplexObjectNode(co: PlainObj): PlainObj | PlainObj[] | unknown[] {
191
+ if ('$COCollectionField' in co && Array.isArray(co['$COCollectionField'])) {
192
+ const arr = co['$COCollectionField'] as unknown[]
193
+ // Primitive arrays (e.g. FlowArray<StringColumn>, FlowArray<IntegerColumn>) store plain
194
+ // strings/numbers in $COCollectionField — not $cv-wrapped objects. Return them directly
195
+ // to avoid decodeComplexObjectItem treating string characters or number properties as fields.
196
+ if (arr.length === 0 || typeof arr[0] !== 'object' || arr[0] === null) {
197
+ return arr
198
+ }
199
+ return (arr as PlainObj[]).map(decodeComplexObjectItem)
200
+ }
201
+ return decodeComplexObjectItem(co)
202
+ }
203
+
204
+ function decodeComplexObjectItem(item: PlainObj): PlainObj {
205
+ const result: PlainObj = {}
206
+ for (const [key, val] of Object.entries(item)) {
207
+ if (key === 'name$' || key.includes('.$')) {
208
+ continue
209
+ }
210
+ if (Array.isArray(val)) {
211
+ // Nested FlowArray<primitive> (Array.String, Array.Integer, Array.Boolean, etc.)
212
+ // within a FlowObject: buildCv stores plain values directly as a bare array —
213
+ // no $COCollectionField wrapper for primitive element types.
214
+ // Must check Array.isArray BEFORE typeof object because arrays are objects in JS.
215
+ if (val.length === 0 || typeof val[0] !== 'object' || val[0] === null) {
216
+ result[key] = val
217
+ } else {
218
+ result[key] = (val as PlainObj[]).map(decodeComplexObjectItem)
219
+ }
220
+ } else if (typeof val === 'object' && val !== null) {
221
+ const v = val as PlainObj
222
+ if ('$cv' in v) {
223
+ // ServiceNow UI format: leaf value wrapped in { $cv: { $c, $v } }
224
+ const cv = v['$cv'] as PlainObj
225
+ const rawVal = cv?.['$v'] ?? ''
226
+ const javaClass = cv?.['$c'] as string | undefined
227
+ // Only coerce to number for known numeric Java types — avoids silently converting
228
+ // string choice values (e.g. state: '1' stored as java.lang.String) to numbers.
229
+ const isNumericType =
230
+ javaClass === 'java.lang.Integer' ||
231
+ javaClass === 'java.lang.Long' ||
232
+ javaClass === 'java.lang.Double' ||
233
+ javaClass === 'java.lang.Float'
234
+ result[key] =
235
+ isNumericType && typeof rawVal === 'string' && rawVal.trim() !== '' ? Number(rawVal) : rawVal
236
+ } else if ('$COCollectionField' in v) {
237
+ // Nested FlowArray<object> (Array.Object) within a FlowObject:
238
+ // buildCv wraps complex array elements in { $COCollectionField: [...] }.
239
+ const arr = v['$COCollectionField'] as unknown[]
240
+ if (arr.length === 0 || typeof arr[0] !== 'object' || arr[0] === null) {
241
+ result[key] = arr
242
+ } else {
243
+ result[key] = (arr as PlainObj[]).map(decodeComplexObjectItem)
244
+ }
245
+ } else {
246
+ // Nested FlowObject
247
+ result[key] = decodeComplexObjectItem(v)
248
+ }
249
+ } else if (val !== undefined) {
250
+ // Fluent build format: raw primitive stored directly (no $cv wrapping).
251
+ // buildArrayPayload skips buildCv for array elements, so string/number/boolean
252
+ // values appear as-is in $COCollectionField items.
253
+ result[key] = val
254
+ }
255
+ }
256
+ return result
257
+ }
258
+
144
259
  /**
145
260
  * Normalize raw input value based on uiType.
146
261
  * - Attempts to JSON.parse strings.
@@ -243,7 +358,13 @@ function buildInstanceToShape({
243
358
 
244
359
  const definitionInputs =
245
360
  instanceDef instanceof Record
246
- ? instanceDef.flat().filter((v) => v.getTable() === inputsTableName)
361
+ ? instanceDef
362
+ .flat()
363
+ .filter(
364
+ (v) =>
365
+ v.getTable() === inputsTableName &&
366
+ getRefSysId(v.get('model')) === instanceDef.getId().getValue()
367
+ )
247
368
  : undefined
248
369
 
249
370
  const zippedInputs = record.get(zippedColumn)?.ifString()?.getValue() ?? ''
@@ -585,6 +706,36 @@ function buildInlineScriptShapeFromXml(
585
706
  return undefined
586
707
  }
587
708
 
709
+ /**
710
+ * Reconstructs a TemplateValueShape with FDInlineScriptCallShape sub-fields when the
711
+ * V2 JSON payload carries scripted sub-fields (scriptActive=false at top level, with
712
+ * a `script` map keyed by sub-field name).
713
+ *
714
+ * Called during XML → Fluent transform to restore wfa.inlineScript() calls that were
715
+ * inside TemplateValue({...}) in the original Fluent source.
716
+ */
717
+ export function applyTemplateValueSubFieldScripts(
718
+ value: unknown,
719
+ script: { [key: string]: { scriptActive: boolean; script: string } } | undefined,
720
+ source: Record
721
+ ): unknown {
722
+ if (!script || !(value instanceof TemplateValueShape)) {
723
+ return value
724
+ }
725
+
726
+ const templateValue = value.getTemplateValue()
727
+ const newProps: globalThis.Record<string, unknown> = {}
728
+ for (const [field, fieldShape] of templateValue.entries({ resolve: false })) {
729
+ const fieldScript = script[field]
730
+ if (fieldScript?.scriptActive && fieldScript?.script) {
731
+ newProps[field] = new FDInlineScriptCallShape({ source, scriptContent: fieldScript.script })
732
+ } else {
733
+ newProps[field] = fieldShape
734
+ }
735
+ }
736
+ return new TemplateValueShape({ source, value: newProps })
737
+ }
738
+
588
739
  /**
589
740
  * Build an ObjectShape representing instance inputs from a base64-zipped JSON payload.
590
741
  * This is used by both subflow and action instances.
@@ -688,9 +839,29 @@ function buildInputsShapeFromZipped({
688
839
  )
689
840
  }
690
841
  }
691
- // Use the specialized shape or fallback to normalized value
692
- props[name] =
693
- inputShape ?? normalizeInputValue(value as string, uiType ?? parameter.type, record, logger)
842
+ // Use the specialized shape or fallback to normalized value.
843
+ // Complex-object (FlowObject/FlowArray) inputs: decode the $cv payload to a
844
+ // plain object so typed action references (Path 1) satisfy TypeScript types
845
+ // and the build's resolveComplexInput path re-encodes them correctly.
846
+ // If decode fails (empty payload / spoke references with no data), fall back
847
+ // to the verbatim string so Path 2 (snapshot/string reference) still works.
848
+ // applyTemplateValueSubFieldScripts reconstructs FDInlineScriptCallShape
849
+ // sub-fields for TemplateValue inputs that carry a `script` map.
850
+ let baseShape: unknown
851
+ if (inputShape) {
852
+ baseShape = inputShape
853
+ } else if (getComplexObjectPayloadType(value) !== undefined) {
854
+ // Only decode when the action schema is available (Path 1: typed action reference,
855
+ // definitionInputs is populated from sys_hub_action_input records).
856
+ // For Path 2 (snapshot/string reference, definitionInputs is undefined),
857
+ // keep the verbatim string so build can re-emit it byte-for-byte.
858
+ const decoded =
859
+ definitionInputs !== undefined ? decodeComplexObjectPayload(value as string) : undefined
860
+ baseShape = decoded !== undefined ? decoded : value
861
+ } else {
862
+ baseShape = normalizeInputValue(value as string, uiType ?? parameter.type, record, logger)
863
+ }
864
+ props[name] = applyTemplateValueSubFieldScripts(baseShape, script, record)
694
865
  }
695
866
  } catch (e) {
696
867
  const recordInfo = `${record.getTable()}.${record.getId().getValue()}`
@@ -940,6 +1111,22 @@ function processInputValue(
940
1111
  return primitiveValue
941
1112
  }
942
1113
 
1114
+ // Preserve path: the input value is already a serialized FD complex-object payload
1115
+ // (FlowObject/FlowArray). Re-emit it verbatim with the correct parameter.type instead
1116
+ // of reconstructing it — works for typed, string/sys_id, and spoke references alike.
1117
+ const preservedComplexType = getComplexObjectPayloadType(primitiveValue)
1118
+ if (preservedComplexType !== undefined) {
1119
+ return {
1120
+ name: ELEMENT_MAPPING_FIELD_ALIASES_REVERSE[inputName] ?? inputName,
1121
+ value: primitiveValue,
1122
+ displayValue: displayValues?.has(inputName) ? displayValues.get(inputName) : primitiveValue,
1123
+ scriptActive: false,
1124
+ parameter: {
1125
+ type: preservedComplexType,
1126
+ },
1127
+ }
1128
+ }
1129
+
943
1130
  let finalValue: unknown = primitiveValue
944
1131
  let finalType: unknown = options?.type ?? 'string'
945
1132
 
@@ -958,7 +1145,7 @@ function processInputValue(
958
1145
 
959
1146
  // Return standard input object
960
1147
  return {
961
- name: inputName,
1148
+ name: ELEMENT_MAPPING_FIELD_ALIASES_REVERSE[inputName] ?? inputName,
962
1149
  value: finalValue,
963
1150
  displayValue: displayValues && displayValues.has(inputName) ? displayValues.get(inputName) : finalValue,
964
1151
  scriptActive: false,
@@ -1090,7 +1277,13 @@ function validateMandatoryFields(
1090
1277
  definitionName?: string
1091
1278
  ): Record[] | undefined {
1092
1279
  const inputDefinitions = definition
1093
- ? definition.flat().filter((record) => record.getTable() === inputTable)
1280
+ ? definition
1281
+ .flat()
1282
+ .filter(
1283
+ (record) =>
1284
+ record.getTable() === inputTable &&
1285
+ getRefSysId(record.get('model')) === definition.getId().getValue()
1286
+ )
1094
1287
  : undefined
1095
1288
 
1096
1289
  if (inputDefinitions) {
@@ -1366,6 +1559,9 @@ async function prepareActionInstanceValueJson(
1366
1559
  properties: Object.fromEntries(mergedResults),
1367
1560
  })
1368
1561
 
1562
+ // Collect scripted sub-fields for any TemplateValue inputs
1563
+ const tvScripts = extractTemplateValueScripts(instanceInputs)
1564
+
1369
1565
  const result: unknown[] = []
1370
1566
 
1371
1567
  // When isActionDefString is true, there's no action definition in fluent
@@ -1427,6 +1623,18 @@ async function prepareActionInstanceValueJson(
1427
1623
  }
1428
1624
  }
1429
1625
 
1626
+ // Attach scripted sub-fields for any TemplateValue inputs
1627
+ if (tvScripts.size > 0) {
1628
+ for (const item of result) {
1629
+ if (typeof item === 'object' && item !== null && 'name' in item) {
1630
+ const scripts = tvScripts.get((item as globalThis.Record<string, unknown>)['name'] as string)
1631
+ if (scripts) {
1632
+ ;(item as globalThis.Record<string, unknown>)['script'] = scripts
1633
+ }
1634
+ }
1635
+ }
1636
+ }
1637
+
1430
1638
  return result
1431
1639
  }
1432
1640
 
@@ -1459,7 +1667,10 @@ async function prepareSubflowInstanceValueJson(
1459
1667
  properties: Object.fromEntries(mergedResults),
1460
1668
  })
1461
1669
 
1462
- return objShape
1670
+ // Collect scripted sub-fields for any TemplateValue inputs
1671
+ const tvScripts = extractTemplateValueScripts(instanceInputs)
1672
+
1673
+ const subflowResult = objShape
1463
1674
  .keys()
1464
1675
  .filter((key) => key !== 'waitForCompletion')
1465
1676
  .map((key) => {
@@ -1496,6 +1707,20 @@ async function prepareSubflowInstanceValueJson(
1496
1707
 
1497
1708
  return result
1498
1709
  })
1710
+
1711
+ // Attach scripted sub-fields for any TemplateValue inputs
1712
+ if (tvScripts.size > 0) {
1713
+ for (const item of subflowResult) {
1714
+ if (typeof item === 'object' && item !== null && 'name' in item) {
1715
+ const scripts = tvScripts.get((item as globalThis.Record<string, unknown>)['name'] as string)
1716
+ if (scripts) {
1717
+ ;(item as globalThis.Record<string, unknown>)['script'] = scripts
1718
+ }
1719
+ }
1720
+ }
1721
+ }
1722
+
1723
+ return subflowResult
1499
1724
  }
1500
1725
 
1501
1726
  /**
@@ -1559,6 +1784,8 @@ async function resolveObjectShapeRecursively(shape: ObjectShape, transform: Tran
1559
1784
  }
1560
1785
 
1561
1786
  resolvedObject[key] = resolvedArray
1787
+ } else if (valueShape instanceof FDInlineScriptCallShape) {
1788
+ resolvedObject[key] = 'fd-scripted'
1562
1789
  } else {
1563
1790
  // For all other shapes (primitives), get the value directly
1564
1791
  resolvedObject[key] = valueShape.getValue()
@@ -1600,6 +1827,36 @@ function wrapSpecialShape(shape: Shape, resolvedValue: unknown, source: Source):
1600
1827
  return resolvedValue
1601
1828
  }
1602
1829
 
1830
+ /**
1831
+ * Extracts scripted sub-fields from any TemplateValue inputs.
1832
+ * Returns a map of input name → { field: { scriptActive, script } } for inputs where
1833
+ * at least one sub-field is an FDInlineScriptCallShape (wfa.inlineScript()).
1834
+ * This map is then attached as a `script` property on the corresponding result item so
1835
+ * ServiceNow can store the script content separately (sys_hub_input_scripts).
1836
+ */
1837
+ function extractTemplateValueScripts(
1838
+ instanceInputs: ObjectShape
1839
+ ): Map<string, globalThis.Record<string, { scriptActive: true; script: string }>> {
1840
+ const result = new Map()
1841
+ for (const [key, shape] of instanceInputs.entries({ resolve: false })) {
1842
+ if (!shape.is(TemplateValueShape)) {
1843
+ continue
1844
+ }
1845
+ const scriptedFields: globalThis.Record<string, { scriptActive: true; script: string }> = {}
1846
+ for (const [field, fieldShape] of (shape as TemplateValueShape)
1847
+ .getTemplateValue()
1848
+ .entries({ resolve: false })) {
1849
+ if (fieldShape instanceof FDInlineScriptCallShape) {
1850
+ scriptedFields[field] = { scriptActive: true, script: fieldShape.getScriptContent() }
1851
+ }
1852
+ }
1853
+ if (Object.keys(scriptedFields).length > 0) {
1854
+ result.set(key, scriptedFields)
1855
+ }
1856
+ }
1857
+ return result
1858
+ }
1859
+
1603
1860
  /**
1604
1861
  * Checks for datapills and resolves them in instance inputs.
1605
1862
  * Similar to resolveObjectShapeRecursively but returns key-value pairs and handles special shapes.
@@ -30,7 +30,8 @@ import {
30
30
  FLOW_OBJECT_API_NAME,
31
31
  FLOW_ARRAY_API_NAME,
32
32
  } from '../utils/flow-constants'
33
- import { normalizeInputValue } from './flow-instance-plugin'
33
+ import { normalizeInputValue, applyTemplateValueSubFieldScripts } from './flow-instance-plugin'
34
+ import { FDInlineScriptCallShape } from './inline-script-plugin'
34
35
  import { stripPillType, collectPillTypes } from '../utils/flow-pill-utils'
35
36
  import { ApprovalRulesShape, ApprovalDueDateShape } from '../utils/flow-shapes'
36
37
  import {
@@ -372,7 +373,10 @@ export const StepInstancePlugin = Plugin.create({
372
373
  coalesce: ['name'],
373
374
  },
374
375
  sys_element_mapping: {
375
- coalesce: ['field', 'id'],
376
+ coalesce: ['field', 'table', 'id'],
377
+ },
378
+ sys_hub_input_scripts: {
379
+ coalesce: ['instance', 'input_name'],
376
380
  },
377
381
  sys_hub_step_instance: {
378
382
  relationships: {
@@ -396,6 +400,10 @@ export const StepInstancePlugin = Plugin.create({
396
400
  via: 'documentkey',
397
401
  descendant: true,
398
402
  },
403
+ sys_hub_input_scripts: {
404
+ via: 'instance',
405
+ descendant: true,
406
+ },
399
407
  },
400
408
  toShape(record, { descendants, logger }) {
401
409
  // Skip step instances that belong to an action definition —
@@ -491,6 +499,32 @@ export const StepInstancePlugin = Plugin.create({
491
499
  : builtInDef.name
492
500
  }
493
501
 
502
+ // Recover wfa.inlineScript() sub-fields from sys_hub_input_scripts descendants.
503
+ // Without this, TemplateValue fields with fd-scripted placeholders would round-trip
504
+ // as TemplateValue({ assignment_group: 'fd-scripted' }) instead of
505
+ // TemplateValue({ assignment_group: wfa.inlineScript('...') }).
506
+ const inputScripts = descendants.query('sys_hub_input_scripts')
507
+ for (const scriptRecord of inputScripts) {
508
+ const inputName = scriptRecord.get('input_name')?.asString()?.getValue()
509
+ const scriptJson = scriptRecord.get('script')?.asString()?.getValue()
510
+ if (!inputName || !scriptJson || configObj[inputName] === undefined) {
511
+ continue
512
+ }
513
+ try {
514
+ const scriptedFields = JSON.parse(scriptJson) as globalThis.Record<
515
+ string,
516
+ { scriptActive: boolean; script: string }
517
+ >
518
+ configObj[inputName] = applyTemplateValueSubFieldScripts(
519
+ configObj[inputName],
520
+ scriptedFields,
521
+ record
522
+ )
523
+ } catch {
524
+ // malformed script JSON — leave the existing value unchanged
525
+ }
526
+ }
527
+
494
528
  // Build step instance config (2nd arg): $id, label, error_handling_type
495
529
  const configProperties: globalThis.Record<string, unknown> = {
496
530
  $id: NowIdShape.from(record),
@@ -1079,6 +1113,9 @@ async function createVariableRecords(
1079
1113
  // Handle TemplateValueShape specially - serialize to ServiceNow format.
1080
1114
  // When TemplateValue contains datapills, the platform stores the entire encoded string
1081
1115
  // (with pills inline) in a single sys_element_mapping record with field=<parent input name>.
1116
+ // Scripted sub-fields (wfa.inlineScript()) use the 'fd-scripted' placeholder in the
1117
+ // encoded value and have their script content stored in a sys_hub_input_scripts record.
1118
+ const scriptedSubFields: globalThis.Record<string, { scriptActive: true; script: string }> = {}
1082
1119
  let actualValue: unknown
1083
1120
  if (valueShape.is(TemplateValueShape)) {
1084
1121
  const templateObj = (valueShape as TemplateValueShape).getTemplateValue()
@@ -1091,6 +1128,10 @@ async function createVariableRecords(
1091
1128
  collectPillTypes(pillString, pillTypeMap)
1092
1129
  entries.push(`${field}=${stripPillType(pillString)}`)
1093
1130
  hasPills = true
1131
+ } else if (fieldShape instanceof FDInlineScriptCallShape) {
1132
+ // Use fd-scripted placeholder; script content goes to sys_hub_input_scripts
1133
+ entries.push(`${field}=fd-scripted`)
1134
+ scriptedSubFields[field] = { scriptActive: true, script: fieldShape.getScriptContent() }
1094
1135
  } else if (fieldShape.is(TemplateExpressionShape)) {
1095
1136
  const resolved = resolveTemplateExpression(
1096
1137
  fieldShape as TemplateExpressionShape,
@@ -1111,6 +1152,8 @@ async function createVariableRecords(
1111
1152
 
1112
1153
  const encodedValue = entries.join('^')
1113
1154
 
1155
+ const hasScripts = Object.keys(scriptedSubFields).length > 0
1156
+
1114
1157
  if (hasPills) {
1115
1158
  // TemplateValue with datapills → single sys_element_mapping record
1116
1159
  // No sys_variable_value for this field — platform reads from element_mapping only
@@ -1126,6 +1169,19 @@ async function createVariableRecords(
1126
1169
  },
1127
1170
  })
1128
1171
  )
1172
+ if (hasScripts) {
1173
+ allRecords.push(
1174
+ await factory.createRecord({
1175
+ source: callExpression,
1176
+ table: 'sys_hub_input_scripts',
1177
+ properties: {
1178
+ instance: stepInstanceSysId,
1179
+ input_name: key,
1180
+ script: JSON.stringify(scriptedSubFields),
1181
+ },
1182
+ })
1183
+ )
1184
+ }
1129
1185
  return
1130
1186
  }
1131
1187
 
@@ -1236,6 +1292,22 @@ async function createVariableRecords(
1236
1292
  },
1237
1293
  })
1238
1294
  allRecords.push(record)
1295
+
1296
+ // For TemplateValue with scripted sub-fields (no datapills path),
1297
+ // create sys_hub_input_scripts record alongside the sys_variable_value
1298
+ if (Object.keys(scriptedSubFields).length > 0) {
1299
+ allRecords.push(
1300
+ await factory.createRecord({
1301
+ source: callExpression,
1302
+ table: 'sys_hub_input_scripts',
1303
+ properties: {
1304
+ instance: stepInstanceSysId,
1305
+ input_name: key,
1306
+ script: JSON.stringify(scriptedSubFields),
1307
+ },
1308
+ })
1309
+ )
1310
+ }
1239
1311
  })
1240
1312
  )
1241
1313
  return allRecords
@@ -1,4 +1,4 @@
1
- import type { PostInstallTask, PostInstallContext } from '@servicenow/sdk-build-core'
1
+ import type { PostInstallTask, PostInstallContext, RecordEntry } from '@servicenow/sdk-build-core'
2
2
 
3
3
  type ActivateFlowsResult = {
4
4
  status: string
@@ -16,8 +16,11 @@ type ActivateFlowsResult = {
16
16
  }>
17
17
  }
18
18
 
19
+ const FLOW_TABLE = 'sys_hub_flow'
20
+ const ACTION_TABLE = 'sys_hub_action_type_definition'
21
+
19
22
  async function activateFlows(context: PostInstallContext): Promise<void> {
20
- const { instanceClient, logger, config } = context
23
+ const { instanceClient, logger, config, recordIds } = context
21
24
  const { scopeId } = config
22
25
 
23
26
  if (!instanceClient) {
@@ -25,13 +28,28 @@ async function activateFlows(context: PostInstallContext): Promise<void> {
25
28
  return
26
29
  }
27
30
 
28
- logger.debug('Activating flows...')
31
+ const flowAndSubflowIds = recordIds?.[FLOW_TABLE] ?? []
32
+ const actionIds = recordIds?.[ACTION_TABLE] ?? []
33
+
34
+ if (flowAndSubflowIds.length === 0 && actionIds.length === 0) {
35
+ logger.debug('No flows, subflows, or actions found in project, skipping flow activation')
36
+ return
37
+ }
38
+
39
+ logger.debug(`Activating flows... (${flowAndSubflowIds.length} flow/subflow(s), ${actionIds.length} action(s))`)
29
40
 
30
41
  const response = await instanceClient.fetch(
31
42
  'api/now/wfa_fluent/activate_flows',
32
43
  {
33
44
  method: 'POST',
34
- headers: { Accept: 'application/json' },
45
+ headers: {
46
+ Accept: 'application/json',
47
+ 'Content-Type': 'application/json',
48
+ },
49
+ body: JSON.stringify({
50
+ flows: flowAndSubflowIds,
51
+ actions: actionIds,
52
+ }),
35
53
  },
36
54
  new URLSearchParams({ sysparm_transaction_scope: scopeId })
37
55
  )
@@ -40,7 +58,7 @@ async function activateFlows(context: PostInstallContext): Promise<void> {
40
58
  // Any other non-OK status (400, 401, 403, 500, etc.) is an error
41
59
  if (!response.ok && response.status !== 422) {
42
60
  const body = await response.json().catch(() => null)
43
- const msg = body?.error?.message ?? body?.message ?? response.statusText
61
+ const msg = body?.result?.error?.message ?? body?.error?.message ?? body?.message ?? response.statusText
44
62
 
45
63
  // Instances without the endpoint return 400 with "does not represent any resource"
46
64
  if (msg.includes('does not represent any resource')) {
@@ -84,6 +102,19 @@ async function activateFlows(context: PostInstallContext): Promise<void> {
84
102
  logger.warn(msg)
85
103
  }
86
104
 
105
+ export function getRecordIdsByTable(keysRegistry: Now.Internal.KeysRegistry): Record<string, RecordEntry[]> {
106
+ const result: Record<string, RecordEntry[]> = {}
107
+
108
+ for (const entry of Object.values(keysRegistry.explicit)) {
109
+ if (entry.deleted) {
110
+ continue
111
+ }
112
+ const entries = (result[entry.table] ??= [])
113
+ entries.push({ sys_id: entry.id, active: '', state: '' })
114
+ }
115
+ return result
116
+ }
117
+
87
118
  export const FlowActivationTask: PostInstallTask = {
88
119
  name: 'flow-activation',
89
120
  skipFlag: 'skipFlowActivation',
@@ -850,8 +850,10 @@ function buildCv(typeNode: unknown, valueNode: unknown): unknown {
850
850
  // Special handling for primitive arrays (schema: ["Any"], value: ["item1", "item2", ...])
851
851
  if (Array.isArray(typeNode) && Array.isArray(valueNode)) {
852
852
  if (typeNode.length === 1 && typeNode[0] === 'Any') {
853
- // Primitive array: return plain string values without $cv wrapping
854
- return valueNode.map((v) => String(v))
853
+ // Primitive array: preserve native types (string, number, boolean) without $cv wrapping.
854
+ // Do NOT force String(v) — that would convert [10,20,30] to ['10','20','30'] and
855
+ // [true,false] to ['true','false'], breaking the round-trip for IntegerColumn/BooleanColumn.
856
+ return valueNode
855
857
  }
856
858
  }
857
859