@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
|
@@ -5,11 +5,18 @@ import {
|
|
|
5
5
|
Shape,
|
|
6
6
|
VariableStatementShape,
|
|
7
7
|
ObjectShape,
|
|
8
|
+
gunzipSync,
|
|
8
9
|
type Diagnostics,
|
|
9
10
|
type Logger,
|
|
10
11
|
} from '@servicenow/sdk-build-core'
|
|
11
12
|
import { ArrowFunctionShape } from '../../arrow-function-plugin'
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
DO_IN_PARALLEL_BLOCK_SYS_ID,
|
|
15
|
+
FLOW_LOGIC,
|
|
16
|
+
FLOW_LOGIC_PREFIX,
|
|
17
|
+
FLOW_LOGIC_SYS_ID_MAP,
|
|
18
|
+
TRY_CATCH_CATCH_SYS_ID,
|
|
19
|
+
} from '../flow-logic/flow-logic-constants'
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Result of datapill transformation
|
|
@@ -22,7 +29,7 @@ export interface DatapillTransformResult {
|
|
|
22
29
|
/**
|
|
23
30
|
* Information about a flow instance shape for processing
|
|
24
31
|
*/
|
|
25
|
-
|
|
32
|
+
interface FlowInstanceShapeInfo {
|
|
26
33
|
callExpression: CallExpressionShape
|
|
27
34
|
variableStatement: VariableStatementShape | CallExpressionShape
|
|
28
35
|
}
|
|
@@ -57,7 +64,7 @@ export function buildUuidToIdentifierMap(recordToShapeMap: Map<Record, Shape>):
|
|
|
57
64
|
|
|
58
65
|
for (const [record, instanceShape] of recordToShapeMap.entries()) {
|
|
59
66
|
// Extract ui_id from the record (this is what datapills reference)
|
|
60
|
-
const uiId = record.get('ui_id')?.getValue()
|
|
67
|
+
const uiId = record.get('ui_id')?.ifString()?.getValue()
|
|
61
68
|
|
|
62
69
|
if (!uiId) {
|
|
63
70
|
continue
|
|
@@ -66,6 +73,14 @@ export function buildUuidToIdentifierMap(recordToShapeMap: Map<Record, Shape>):
|
|
|
66
73
|
// Handle VariableStatementShape (actions/subflows with variable assignments)
|
|
67
74
|
if (instanceShape instanceof VariableStatementShape) {
|
|
68
75
|
const variableStatement = instanceShape.as(VariableStatementShape)
|
|
76
|
+
const initCallee =
|
|
77
|
+
variableStatement.getInitializer() instanceof CallExpressionShape
|
|
78
|
+
? (variableStatement.getInitializer() as CallExpressionShape).getCallee()
|
|
79
|
+
: undefined
|
|
80
|
+
if (initCallee === FLOW_LOGIC.DO_IN_PARALLEL || initCallee === FLOW_LOGIC.TRY_CATCH) {
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
const variableName = variableStatement.getVariableName()
|
|
70
85
|
const callExpression = variableStatement.getInitializer().as(CallExpressionShape)
|
|
71
86
|
|
|
@@ -119,6 +134,227 @@ export function buildUuidToIdentifierMap(recordToShapeMap: Map<Record, Shape>):
|
|
|
119
134
|
return uuidToIdentifierMap
|
|
120
135
|
}
|
|
121
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Build a ui_id → ancestor-chain map for the given instances. Each entry's value is the set of all `ui_id`s in the
|
|
139
|
+
* record's ancestor chain, including the record itself. Useful when resolving pills: a source record that lives
|
|
140
|
+
* "inside the same branch" as the target action shares the target's branch `ui_id` in this set, and the bare
|
|
141
|
+
* identifier can be used (no outer-binding dot-walk needed).
|
|
142
|
+
*/
|
|
143
|
+
function buildAncestorChainMap(allInstances: ReadonlyArray<Record>): Map<string, Set<string>> {
|
|
144
|
+
const recordByUiId = new Map<string, Record>()
|
|
145
|
+
|
|
146
|
+
for (const inst of allInstances) {
|
|
147
|
+
const ui = inst.get('ui_id')?.ifString()?.getValue()
|
|
148
|
+
if (ui) {
|
|
149
|
+
recordByUiId.set(ui, inst)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const chainByUiId = new Map<string, Set<string>>()
|
|
154
|
+
|
|
155
|
+
const computeChain = (uiId: string, seen: Set<string>): Set<string> => {
|
|
156
|
+
const cached = chainByUiId.get(uiId)
|
|
157
|
+
if (cached) {
|
|
158
|
+
return cached
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (seen.has(uiId)) {
|
|
162
|
+
return new Set([uiId]) // Cycle guard.
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
seen.add(uiId)
|
|
166
|
+
|
|
167
|
+
const chain = new Set<string>([uiId])
|
|
168
|
+
const rec = recordByUiId.get(uiId)
|
|
169
|
+
const parent = rec?.get('parent_ui_id')?.ifString()?.getValue()
|
|
170
|
+
if (parent) {
|
|
171
|
+
for (const ancestor of computeChain(parent, seen)) {
|
|
172
|
+
chain.add(ancestor)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
chainByUiId.set(uiId, chain)
|
|
177
|
+
return chain
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const inst of allInstances) {
|
|
181
|
+
const ui = inst.get('ui_id')?.ifString()?.getValue()
|
|
182
|
+
if (ui) {
|
|
183
|
+
computeChain(ui, new Set())
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return chainByUiId
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Detect whether any action/subflow nested inside a DoInParallel branch or TryCatch arm has its output
|
|
192
|
+
* referenced from outside that branch/arm. Returns true if hoisting is detected (caller should fall back
|
|
193
|
+
* to Record() API), false otherwise.
|
|
194
|
+
*/
|
|
195
|
+
export function detectHoistedOutputBindings(allInstances: ReadonlyArray<Record>): boolean {
|
|
196
|
+
if (allInstances.length === 0) {
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const tryCatchLogicId = FLOW_LOGIC_SYS_ID_MAP[FLOW_LOGIC.TRY_CATCH]
|
|
201
|
+
|
|
202
|
+
// Collect ui_ids of all DoInParallel branches and TryCatch arms.
|
|
203
|
+
const branchUiIds = new Set<string>()
|
|
204
|
+
for (const inst of allInstances) {
|
|
205
|
+
if (inst.getTable() !== 'sys_hub_flow_logic_instance_v2') {
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
const logicDef = inst.get('logic_definition')?.ifString()?.getValue()
|
|
209
|
+
if (
|
|
210
|
+
logicDef === DO_IN_PARALLEL_BLOCK_SYS_ID ||
|
|
211
|
+
logicDef === TRY_CATCH_CATCH_SYS_ID ||
|
|
212
|
+
(tryCatchLogicId && logicDef === tryCatchLogicId)
|
|
213
|
+
) {
|
|
214
|
+
const uiId = inst.get('ui_id')?.ifString()?.getValue()
|
|
215
|
+
if (uiId) {
|
|
216
|
+
branchUiIds.add(uiId)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (branchUiIds.size === 0) {
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Build ancestor chain map and reference index.
|
|
226
|
+
const ancestorChainMap = buildAncestorChainMap(allInstances)
|
|
227
|
+
const referenceIndex = buildReferenceIndex(allInstances)
|
|
228
|
+
|
|
229
|
+
// Map: action/subflow UUID → the branch/arm ui_id that contains it (directly or transitively).
|
|
230
|
+
// Uses the full ancestor chain so actions nested inside If/ForEach within a branch are also caught.
|
|
231
|
+
const innerActionBranch = new Map<string, string>()
|
|
232
|
+
for (const [uuid, record] of uuidToRecordMap.entries()) {
|
|
233
|
+
const uiId = record.get('ui_id')?.ifString()?.getValue()
|
|
234
|
+
if (!uiId) {
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
const chain = ancestorChainMap.get(uiId)
|
|
238
|
+
if (!chain) {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
for (const branchUiId of branchUiIds) {
|
|
242
|
+
if (chain.has(branchUiId)) {
|
|
243
|
+
innerActionBranch.set(uuid, branchUiId)
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (innerActionBranch.size === 0) {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// For each inner action UUID, check if any source record referencing it lives outside its branch.
|
|
254
|
+
for (const [uuid, branchUiId] of innerActionBranch.entries()) {
|
|
255
|
+
const sources = referenceIndex.get(uuid)
|
|
256
|
+
if (!sources) {
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const sourceUiId of sources) {
|
|
261
|
+
if (!sourceUiId) {
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
const ancestorChain = ancestorChainMap.get(sourceUiId)
|
|
265
|
+
const isInside = ancestorChain ? ancestorChain.has(branchUiId) : false
|
|
266
|
+
if (!isInside) {
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildReferenceIndex(allInstances: ReadonlyArray<Record>): Map<string, Set<string | undefined>> {
|
|
276
|
+
const result = new Map<string, Set<string | undefined>>()
|
|
277
|
+
const uuidRe = /\{\{(?:step\[)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/g
|
|
278
|
+
for (const inst of allInstances) {
|
|
279
|
+
const valuesStr = readValuesForPillScan(inst)
|
|
280
|
+
if (!valuesStr) {
|
|
281
|
+
continue
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const sourceUiId = inst.get('ui_id')?.ifString()?.getValue()
|
|
285
|
+
for (const m of valuesStr.matchAll(uuidRe)) {
|
|
286
|
+
const uuid = m[1]
|
|
287
|
+
if (!uuid) {
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let sources = result.get(uuid)
|
|
292
|
+
if (!sources) {
|
|
293
|
+
sources = new Set<string | undefined>()
|
|
294
|
+
result.set(uuid, sources)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
sources.add(sourceUiId)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return result
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Read an instance record's `values` column for substring-search of datapill UUIDs. ServiceNow stores the values field
|
|
305
|
+
* as a gzipped+base64 string. Returns the decompressed JSON-stringified payload, or `undefined` if the column is
|
|
306
|
+
* missing / unreadable. Errors are swallowed because pill-scan failure is non-critical — the worst case is
|
|
307
|
+
* over-keeping a binding.
|
|
308
|
+
*/
|
|
309
|
+
// Memoizes the decoded `values` column per record. Decoding gzips (via `gunzipSync`) which is expensive, and the same
|
|
310
|
+
// records are scanned repeatedly — once by `buildReferenceIndex`, once per catch arm in `buildCatchParamMap`, etc.
|
|
311
|
+
// Keyed by Record identity (WeakMap), so entries are collected once a flow's records go out of scope.
|
|
312
|
+
const valuesScanCache = new WeakMap<Record, string | undefined>()
|
|
313
|
+
|
|
314
|
+
function readValuesForPillScan(record: Record): string | undefined {
|
|
315
|
+
const cached = valuesScanCache.get(record)
|
|
316
|
+
if (cached !== undefined || valuesScanCache.has(record)) {
|
|
317
|
+
return cached
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const decoded = decodeValuesForPillScan(record)
|
|
321
|
+
valuesScanCache.set(record, decoded)
|
|
322
|
+
|
|
323
|
+
return decoded
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function decodeValuesForPillScan(record: Record): string | undefined {
|
|
327
|
+
const raw = record.get('values')?.getValue()
|
|
328
|
+
if (raw === undefined || raw === null) {
|
|
329
|
+
return undefined
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (typeof raw !== 'string') {
|
|
333
|
+
// Already deserialized — stringify so callers can do substring search.
|
|
334
|
+
try {
|
|
335
|
+
return JSON.stringify(raw)
|
|
336
|
+
} catch {
|
|
337
|
+
return undefined
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (raw === '') {
|
|
342
|
+
return ''
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// gzip magic bytes are `H4sI` when base64-encoded. `gunzipSync` (fflate) takes/returns a Uint8Array, so decode the
|
|
346
|
+
// base64 to bytes on the way in and back to utf8 on the way out.
|
|
347
|
+
if (raw.startsWith('H4sI')) {
|
|
348
|
+
try {
|
|
349
|
+
return Buffer.from(gunzipSync(Buffer.from(raw, 'base64'))).toString('utf8')
|
|
350
|
+
} catch {
|
|
351
|
+
return undefined
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return raw
|
|
356
|
+
}
|
|
357
|
+
|
|
122
358
|
/**
|
|
123
359
|
* Extract CallExpression and VariableStatement from an instance shape
|
|
124
360
|
* Handles both VariableStatementShape (for Action/Subflow) and bare CallExpressionShape (for flow logic)
|
|
@@ -126,7 +362,7 @@ export function buildUuidToIdentifierMap(recordToShapeMap: Map<Record, Shape>):
|
|
|
126
362
|
* @param instanceShape - The shape to extract from
|
|
127
363
|
* @returns Object containing callExpression and variableStatement
|
|
128
364
|
*/
|
|
129
|
-
|
|
365
|
+
function extractInstanceShapeInfo(instanceShape: Shape): FlowInstanceShapeInfo {
|
|
130
366
|
let callExpression: CallExpressionShape
|
|
131
367
|
let variableStatement: VariableStatementShape | CallExpressionShape
|
|
132
368
|
|
|
@@ -141,32 +377,6 @@ export function extractInstanceShapeInfo(instanceShape: Shape): FlowInstanceShap
|
|
|
141
377
|
return { callExpression, variableStatement }
|
|
142
378
|
}
|
|
143
379
|
|
|
144
|
-
/**
|
|
145
|
-
* Rebuild a CallExpressionShape with transformed properties
|
|
146
|
-
* Creates a new CallExpression with the transformed props and cleaned config
|
|
147
|
-
*
|
|
148
|
-
* @param originalCallExpression - The original CallExpression
|
|
149
|
-
* @param transformedProps - The transformed properties object
|
|
150
|
-
* @param configShape - The config shape (possibly cleaned)
|
|
151
|
-
* @param record - The source record for shape creation
|
|
152
|
-
* @returns New CallExpressionShape with transformed properties
|
|
153
|
-
*/
|
|
154
|
-
export function rebuildCallExpressionWithDatapills(
|
|
155
|
-
originalCallExpression: CallExpressionShape,
|
|
156
|
-
transformedProps: { [key: string]: Shape },
|
|
157
|
-
configShape: Shape,
|
|
158
|
-
record: Record
|
|
159
|
-
): CallExpressionShape {
|
|
160
|
-
const originalActionDef = originalCallExpression.getArgument(0, false)
|
|
161
|
-
const newPropsArg = Shape.from(record, transformedProps)
|
|
162
|
-
|
|
163
|
-
return new CallExpressionShape({
|
|
164
|
-
source: originalCallExpression.getSource(),
|
|
165
|
-
callee: originalCallExpression.getCallee(),
|
|
166
|
-
args: [originalActionDef, configShape, newPropsArg],
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
380
|
/**
|
|
171
381
|
* Rebuild an instance shape (VariableStatement or CallExpression) with transformed datapills
|
|
172
382
|
*
|
|
@@ -175,7 +385,7 @@ export function rebuildCallExpressionWithDatapills(
|
|
|
175
385
|
* @param instanceShapeInfo - Information extracted from the instance shape
|
|
176
386
|
* @returns Rebuilt VariableStatementShape or the new CallExpression
|
|
177
387
|
*/
|
|
178
|
-
|
|
388
|
+
function rebuildInstanceWithDatapills(
|
|
179
389
|
instanceShape: Shape,
|
|
180
390
|
newCallExpression: CallExpressionShape,
|
|
181
391
|
instanceShapeInfo: FlowInstanceShapeInfo
|
|
@@ -203,7 +413,7 @@ export function rebuildInstanceWithDatapills(
|
|
|
203
413
|
* @param callee - The callee string (e.g., 'wfa.flowLogic.if', 'wfa.action')
|
|
204
414
|
* @returns true if the callee is a flow logic call
|
|
205
415
|
*/
|
|
206
|
-
|
|
416
|
+
function isFlowLogicCallee(callee: string): boolean {
|
|
207
417
|
return callee.startsWith(FLOW_LOGIC_PREFIX)
|
|
208
418
|
}
|
|
209
419
|
|
|
@@ -212,7 +422,7 @@ export function isFlowLogicCallee(callee: string): boolean {
|
|
|
212
422
|
* @param callee - The flow logic callee (e.g., 'wfa.flowLogic.if')
|
|
213
423
|
* @returns true if the flow logic has a body
|
|
214
424
|
*/
|
|
215
|
-
|
|
425
|
+
function flowLogicHasBody(callee: string): boolean {
|
|
216
426
|
// Flow logic with bodies: if, elseIf, else, forEach, tryCatch, doInParallel
|
|
217
427
|
// Flow logic without bodies: goBackTo, waitForADuration, exitLoop, endFlow, skipIteration
|
|
218
428
|
return [
|
|
@@ -237,7 +447,7 @@ export function flowLogicHasBody(callee: string): boolean {
|
|
|
237
447
|
* @param callee - The flow logic callee
|
|
238
448
|
* @returns Array of arrow function shapes
|
|
239
449
|
*/
|
|
240
|
-
|
|
450
|
+
function extractFlowLogicBodies(callExpression: CallExpressionShape, callee: string): ArrowFunctionShape[] {
|
|
241
451
|
const bodies: ArrowFunctionShape[] = []
|
|
242
452
|
|
|
243
453
|
if (callee === FLOW_LOGIC.TRY_CATCH) {
|
|
@@ -288,7 +498,7 @@ export function extractFlowLogicBodies(callExpression: CallExpressionShape, call
|
|
|
288
498
|
* @param record - The source record
|
|
289
499
|
* @returns Rebuilt CallExpressionShape
|
|
290
500
|
*/
|
|
291
|
-
|
|
501
|
+
function rebuildFlowLogicWithDatapills(
|
|
292
502
|
callExpression: CallExpressionShape,
|
|
293
503
|
transformedConfig: ObjectShape,
|
|
294
504
|
transformedBodies: ArrowFunctionShape[],
|
|
@@ -544,12 +754,14 @@ export function processInstanceForDatapills(
|
|
|
544
754
|
}
|
|
545
755
|
}
|
|
546
756
|
|
|
547
|
-
// Rebuild body if children changed
|
|
757
|
+
// Rebuild body if children changed. Preserve the arrow's `returnValue` if present.
|
|
758
|
+
const returnValue = body.getReturnValue()
|
|
548
759
|
const transformedBody = anyChildChanged
|
|
549
760
|
? new ArrowFunctionShape({
|
|
550
761
|
source: body.getSource(),
|
|
551
762
|
parameters: body.getParameters(),
|
|
552
763
|
statements: transformedChildren,
|
|
764
|
+
...(returnValue !== undefined && { returnValue }),
|
|
553
765
|
})
|
|
554
766
|
: body
|
|
555
767
|
|
|
@@ -545,6 +545,14 @@ export const ELEMENT_MAPPING_FIELD_ALIASES: { [platformName: string]: string } =
|
|
|
545
545
|
__snc_dont_fail_on_error: 'dont_fail_flow_on_error',
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
+
/**
|
|
549
|
+
* Reverse of ELEMENT_MAPPING_FIELD_ALIASES — maps Fluent API names back to their
|
|
550
|
+
* platform-internal names for serialization into sys_hub_action_instance_v2.
|
|
551
|
+
*/
|
|
552
|
+
export const ELEMENT_MAPPING_FIELD_ALIASES_REVERSE: { [fluentName: string]: string } = Object.fromEntries(
|
|
553
|
+
Object.entries(ELEMENT_MAPPING_FIELD_ALIASES).map(([platform, fluent]) => [fluent, platform])
|
|
554
|
+
)
|
|
555
|
+
|
|
548
556
|
export enum ChoiceDropdown {
|
|
549
557
|
DROPDOWN_WITH_NONE = 1,
|
|
550
558
|
DROPDOWN_WITHOUT_NONE = 3,
|
|
@@ -194,6 +194,16 @@ export async function buildVariableRecords(options: {
|
|
|
194
194
|
additionalProperties['reference'] = referenceTable
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
|
|
198
|
+
// Handle field_list and template_value columns - extract dependent field.
|
|
199
|
+
// Always write all three fields so that removing 'dependent' from fluent
|
|
200
|
+
// explicitly clears the old value in ServiceNow rather than leaving it stale.
|
|
201
|
+
if (internal_type === 'field_list' || internal_type === 'template_value') {
|
|
202
|
+
const dependent = config.get('dependent')?.ifString()?.getValue() ?? ''
|
|
203
|
+
additionalProperties['dependent'] = dependent
|
|
204
|
+
additionalProperties['dependent_on_field'] = dependent
|
|
205
|
+
additionalProperties['use_dependent_field'] = dependent !== ''
|
|
206
|
+
}
|
|
197
207
|
additionalProperties['internal_type'] = internal_type
|
|
198
208
|
additionalProperties['max_length'] =
|
|
199
209
|
config.get('maxLength')?.ifNumber()?.getValue() ?? getDefaultMaxLength(internal_type)
|
|
@@ -400,7 +410,11 @@ function buildBasicVariableShape(
|
|
|
400
410
|
// Add dependent field for template_value and field_list column types
|
|
401
411
|
if (fieldType === 'template_value' || fieldType === 'field_list') {
|
|
402
412
|
const dependent = variable.get('dependent')?.ifString()?.getValue()
|
|
403
|
-
|
|
413
|
+
// For field_list, dependent is optional — only emit it when non-empty.
|
|
414
|
+
// For template_value, dependent is required by its type, so always include it.
|
|
415
|
+
if (fieldType === 'template_value' || dependent) {
|
|
416
|
+
additionalProps['dependent'] = dependent ?? ''
|
|
417
|
+
}
|
|
404
418
|
}
|
|
405
419
|
|
|
406
420
|
return new CallExpressionShape({
|
|
@@ -521,7 +535,7 @@ function validateAndPopulateChoiceProperties(
|
|
|
521
535
|
}
|
|
522
536
|
|
|
523
537
|
// Default to dropdown_with_none
|
|
524
|
-
let choiceValue = ChoiceDropdown.
|
|
538
|
+
let choiceValue = ChoiceDropdown.DROPDOWN_WITH_NONE
|
|
525
539
|
|
|
526
540
|
// Handle dropdown type configuration
|
|
527
541
|
const defaultValue = config.get('default')
|
|
@@ -531,23 +545,23 @@ function validateAndPopulateChoiceProperties(
|
|
|
531
545
|
const choiceTypeValue = choiceTypeNode.getValue() as choiceDropdownType
|
|
532
546
|
const choiceDropdownValue = choiceDropdown.indexOf(choiceTypeValue)
|
|
533
547
|
|
|
534
|
-
if (choiceDropdownValue ===
|
|
535
|
-
//
|
|
536
|
-
if (defaultValue.isUndefined()) {
|
|
537
|
-
diagnostics.error(config, 'Default value is required for dropdown type "dropdown_without_none"')
|
|
538
|
-
return
|
|
539
|
-
}
|
|
540
|
-
choiceValue = ChoiceDropdown.DROPDOWN_WITHOUT_NONE
|
|
541
|
-
} else if (choiceDropdownValue !== ChoiceDropdown.DROPDOWN_WITH_NONE) {
|
|
542
|
-
// Only dropdown_with_none and dropdown_without_none are valid
|
|
548
|
+
if (choiceDropdownValue === -1) {
|
|
549
|
+
// Not a recognized dropdown type
|
|
543
550
|
diagnostics.error(
|
|
544
551
|
choiceTypeNode,
|
|
545
|
-
|
|
552
|
+
`Invalid dropdown type "${choiceTypeValue}", expected one of: ${choiceDropdown.join(', ')}`
|
|
546
553
|
)
|
|
547
554
|
return
|
|
548
|
-
} else {
|
|
549
|
-
choiceValue = choiceDropdownValue
|
|
550
555
|
}
|
|
556
|
+
|
|
557
|
+
// dropdown_without_none has no "--None--" entry, so a default value is required
|
|
558
|
+
if (choiceDropdownValue === ChoiceDropdown.DROPDOWN_WITHOUT_NONE && defaultValue.isUndefined()) {
|
|
559
|
+
diagnostics.error(config, 'Default value is required for dropdown type "dropdown_without_none"')
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Preserve whatever valid dropdown type came through (none/with_none/suggestion/without_none)
|
|
564
|
+
choiceValue = choiceDropdownValue
|
|
551
565
|
}
|
|
552
566
|
|
|
553
567
|
// Set the choice value
|
|
@@ -12,6 +12,25 @@ import type { ApprovalRulesType, ApprovalDueDateType } from '@servicenow/sdk-cor
|
|
|
12
12
|
import { approvalRulesJsonToString, approvalRulesStringToJson } from './approval-rules-processor'
|
|
13
13
|
import { APPROVAL_RULES_API_NAME, APPROVAL_DUE_DATE_API_NAME } from './flow-constants'
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Safely extracts a sysId string from a shape that may be either a plain StringShape
|
|
17
|
+
* or a FK-resolved Record. After build+install the SDK resolves FK fields to Record
|
|
18
|
+
* objects, so calling .asString() directly would throw.
|
|
19
|
+
*/
|
|
20
|
+
export function getRefSysId(shape: Shape | undefined): string | undefined {
|
|
21
|
+
if (!shape) {
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
const str = shape.ifString()?.getValue()
|
|
25
|
+
if (str !== undefined) {
|
|
26
|
+
return str
|
|
27
|
+
}
|
|
28
|
+
if (shape instanceof Record) {
|
|
29
|
+
return shape.getId().getValue()
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
15
34
|
/**
|
|
16
35
|
* Abstract base class for all flow definition instances.
|
|
17
36
|
* Extends CallExpressionShape to provide common functionality for flow constructs.
|
|
@@ -7,9 +7,9 @@ import { buildVariableRecords } from './flow-io-to-record'
|
|
|
7
7
|
export type VariableTableType = 'sys_hub_flow_input' | 'sys_hub_flow_output' | 'sys_hub_flow_variable'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Parent table type
|
|
10
|
+
* Parent table type for flow variable/IO records
|
|
11
11
|
*/
|
|
12
|
-
export type ParentTableType = 'sys_hub_flow'
|
|
12
|
+
export type ParentTableType = 'sys_hub_flow' | 'sys_hub_flow_snapshot'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Process a flow variable configuration (inputs, outputs, or flow variables)
|
|
@@ -27,7 +27,8 @@ export async function processVariableConfig(
|
|
|
27
27
|
varTable: VariableTableType,
|
|
28
28
|
parentRecord: Record,
|
|
29
29
|
factory: Factory,
|
|
30
|
-
diagnostics: Diagnostics
|
|
30
|
+
diagnostics: Diagnostics,
|
|
31
|
+
parentTable: ParentTableType = 'sys_hub_flow'
|
|
31
32
|
): Promise<Record[]> {
|
|
32
33
|
if (!config) {
|
|
33
34
|
return []
|
|
@@ -38,7 +39,7 @@ export async function processVariableConfig(
|
|
|
38
39
|
parentRecord,
|
|
39
40
|
factory,
|
|
40
41
|
diagnostics,
|
|
41
|
-
parentTable
|
|
42
|
+
parentTable,
|
|
42
43
|
varTable,
|
|
43
44
|
})
|
|
44
45
|
|
|
@@ -58,9 +59,10 @@ export async function processFlowInputs(
|
|
|
58
59
|
inputsConfig: ObjectShape | undefined,
|
|
59
60
|
parentRecord: Record,
|
|
60
61
|
factory: Factory,
|
|
61
|
-
diagnostics: Diagnostics
|
|
62
|
+
diagnostics: Diagnostics,
|
|
63
|
+
parentTable: ParentTableType = 'sys_hub_flow'
|
|
62
64
|
): Promise<Record[]> {
|
|
63
|
-
return processVariableConfig(inputsConfig, 'sys_hub_flow_input', parentRecord, factory, diagnostics)
|
|
65
|
+
return processVariableConfig(inputsConfig, 'sys_hub_flow_input', parentRecord, factory, diagnostics, parentTable)
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -76,9 +78,10 @@ export async function processFlowOutputs(
|
|
|
76
78
|
outputsConfig: ObjectShape | undefined,
|
|
77
79
|
parentRecord: Record,
|
|
78
80
|
factory: Factory,
|
|
79
|
-
diagnostics: Diagnostics
|
|
81
|
+
diagnostics: Diagnostics,
|
|
82
|
+
parentTable: ParentTableType = 'sys_hub_flow'
|
|
80
83
|
): Promise<Record[]> {
|
|
81
|
-
return processVariableConfig(outputsConfig, 'sys_hub_flow_output', parentRecord, factory, diagnostics)
|
|
84
|
+
return processVariableConfig(outputsConfig, 'sys_hub_flow_output', parentRecord, factory, diagnostics, parentTable)
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
/**
|
|
@@ -94,7 +97,15 @@ export async function processFlowVariables(
|
|
|
94
97
|
flowVariablesConfig: ObjectShape | undefined,
|
|
95
98
|
parentRecord: Record,
|
|
96
99
|
factory: Factory,
|
|
97
|
-
diagnostics: Diagnostics
|
|
100
|
+
diagnostics: Diagnostics,
|
|
101
|
+
parentTable: ParentTableType = 'sys_hub_flow'
|
|
98
102
|
): Promise<Record[]> {
|
|
99
|
-
return processVariableConfig(
|
|
103
|
+
return processVariableConfig(
|
|
104
|
+
flowVariablesConfig,
|
|
105
|
+
'sys_hub_flow_variable',
|
|
106
|
+
parentRecord,
|
|
107
|
+
factory,
|
|
108
|
+
diagnostics,
|
|
109
|
+
parentTable
|
|
110
|
+
)
|
|
100
111
|
}
|