@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.
- package/dist/alias/alias-plugin.d.ts +2 -0
- package/dist/alias/alias-plugin.js +183 -0
- package/dist/alias/alias-plugin.js.map +1 -0
- package/dist/alias/alias-template-plugin.d.ts +2 -0
- package/dist/alias/alias-template-plugin.js +232 -0
- package/dist/alias/alias-template-plugin.js.map +1 -0
- package/dist/alias/index.d.ts +3 -0
- package/dist/alias/index.js +20 -0
- package/dist/alias/index.js.map +1 -0
- package/dist/alias/retry-policy-plugin.d.ts +2 -0
- package/dist/alias/retry-policy-plugin.js +119 -0
- package/dist/alias/retry-policy-plugin.js.map +1 -0
- package/dist/arrow-function-plugin.d.ts +1 -0
- package/dist/arrow-function-plugin.js +60 -21
- package/dist/arrow-function-plugin.js.map +1 -1
- package/dist/atf/test-plugin.js +1 -1
- package/dist/atf/test-plugin.js.map +1 -1
- package/dist/basic-syntax-plugin.js +7 -7
- package/dist/basic-syntax-plugin.js.map +1 -1
- package/dist/column/index.d.ts +2 -0
- package/dist/column/index.js +13 -0
- package/dist/column/index.js.map +1 -0
- package/dist/dashboard/dashboard-plugin.js +4 -0
- package/dist/dashboard/dashboard-plugin.js.map +1 -1
- package/dist/data-lookup-plugin.d.ts +2 -0
- package/dist/data-lookup-plugin.js +159 -0
- package/dist/data-lookup-plugin.js.map +1 -0
- package/dist/flow/flow-logic/flow-logic-diagnostics.js +2 -1
- package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
- package/dist/flow/flow-logic/flow-logic-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-action-definition-plugin.js +81 -16
- package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-definition-plugin.js +70 -7
- package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-instance-plugin.d.ts +35 -1
- package/dist/flow/plugins/flow-instance-plugin.js +241 -7
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
- package/dist/flow/plugins/step-instance-plugin.js +61 -1
- package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
- package/dist/flow/post-install.d.ts +2 -1
- package/dist/flow/post-install.js +31 -4
- package/dist/flow/post-install.js.map +1 -1
- package/dist/flow/utils/complex-object-resolver.js +4 -2
- package/dist/flow/utils/complex-object-resolver.js.map +1 -1
- package/dist/flow/utils/datapill-transformer.d.ts +5 -72
- package/dist/flow/utils/datapill-transformer.js +199 -28
- package/dist/flow/utils/datapill-transformer.js.map +1 -1
- package/dist/flow/utils/flow-constants.d.ts +7 -0
- package/dist/flow/utils/flow-constants.js +6 -1
- package/dist/flow/utils/flow-constants.js.map +1 -1
- package/dist/flow/utils/flow-io-to-record.js +24 -15
- package/dist/flow/utils/flow-io-to-record.js.map +1 -1
- package/dist/flow/utils/flow-shapes.d.ts +8 -2
- package/dist/flow/utils/flow-shapes.js +19 -0
- package/dist/flow/utils/flow-shapes.js.map +1 -1
- package/dist/flow/utils/flow-variable-processor.d.ts +6 -6
- package/dist/flow/utils/flow-variable-processor.js +8 -8
- package/dist/flow/utils/flow-variable-processor.js.map +1 -1
- package/dist/form-plugin.js +35 -24
- package/dist/form-plugin.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/now-attach-plugin.d.ts +1 -1
- package/dist/now-config-plugin.js +2 -1
- package/dist/now-config-plugin.js.map +1 -1
- package/dist/now-delete-plugin.d.ts +2 -0
- package/dist/now-delete-plugin.js +64 -0
- package/dist/now-delete-plugin.js.map +1 -0
- package/dist/record-plugin.d.ts +10 -0
- package/dist/record-plugin.js +15 -1
- package/dist/record-plugin.js.map +1 -1
- package/dist/repack/lint/Rules.js +17 -7
- package/dist/repack/lint/Rules.js.map +1 -1
- package/dist/rest-message-plugin.d.ts +2 -0
- package/dist/rest-message-plugin.js +331 -0
- package/dist/rest-message-plugin.js.map +1 -0
- package/dist/script-include-plugin.js +1 -1
- package/dist/script-include-plugin.js.map +1 -1
- package/dist/server-module-plugin/sbom-builder.js +17 -7
- package/dist/server-module-plugin/sbom-builder.js.map +1 -1
- package/dist/static-content-plugin.js +17 -7
- package/dist/static-content-plugin.js.map +1 -1
- package/package.json +7 -6
- package/src/alias/alias-plugin.ts +221 -0
- package/src/alias/alias-template-plugin.ts +271 -0
- package/src/alias/index.ts +3 -0
- package/src/alias/retry-policy-plugin.ts +138 -0
- package/src/arrow-function-plugin.ts +67 -23
- package/src/atf/test-plugin.ts +1 -1
- package/src/basic-syntax-plugin.ts +7 -7
- package/src/column/index.ts +7 -0
- package/src/dashboard/dashboard-plugin.ts +4 -0
- package/src/data-lookup-plugin.ts +191 -0
- package/src/flow/flow-logic/flow-logic-diagnostics.ts +2 -1
- package/src/flow/flow-logic/flow-logic-plugin.ts +0 -1
- package/src/flow/plugins/flow-action-definition-plugin.ts +92 -25
- package/src/flow/plugins/flow-definition-plugin.ts +114 -8
- package/src/flow/plugins/flow-instance-plugin.ts +264 -7
- package/src/flow/plugins/step-instance-plugin.ts +74 -2
- package/src/flow/post-install.ts +36 -5
- package/src/flow/utils/complex-object-resolver.ts +4 -2
- package/src/flow/utils/datapill-transformer.ts +248 -36
- package/src/flow/utils/flow-constants.ts +8 -0
- package/src/flow/utils/flow-io-to-record.ts +28 -14
- package/src/flow/utils/flow-shapes.ts +19 -0
- package/src/flow/utils/flow-variable-processor.ts +21 -10
- package/src/form-plugin.ts +47 -26
- package/src/index.ts +5 -1
- package/src/now-config-plugin.ts +2 -1
- package/src/now-delete-plugin.ts +82 -0
- package/src/record-plugin.ts +17 -2
- package/src/rest-message-plugin.ts +391 -0
- 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
|
|
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
|
-
|
|
693
|
-
|
|
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
|
|
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
|
-
|
|
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
|
package/src/flow/post-install.ts
CHANGED
|
@@ -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
|
-
|
|
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: {
|
|
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:
|
|
854
|
-
|
|
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
|
|