@servicenow/sdk-build-plugins 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/column-plugin.js +3 -7
  2. package/dist/column-plugin.js.map +1 -1
  3. package/dist/flow/flow-logic/flow-logic-diagnostics.js +5 -5
  4. package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
  5. package/dist/flow/plugins/flow-action-definition-plugin.js +1229 -54
  6. package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
  7. package/dist/flow/plugins/flow-data-pill-plugin.js +5 -2
  8. package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
  9. package/dist/flow/plugins/flow-definition-plugin.js +16 -42
  10. package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
  11. package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +2 -2
  12. package/dist/flow/plugins/flow-diagnostics-plugin.js +2 -2
  13. package/dist/flow/plugins/flow-instance-plugin.js +68 -22
  14. package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
  15. package/dist/flow/plugins/step-definition-plugin.js +2 -1
  16. package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
  17. package/dist/flow/plugins/step-instance-plugin.d.ts +9 -1
  18. package/dist/flow/plugins/step-instance-plugin.js +649 -136
  19. package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
  20. package/dist/flow/plugins/wfa-datapill-plugin.js +20 -5
  21. package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
  22. package/dist/flow/post-install.js +1 -0
  23. package/dist/flow/post-install.js.map +1 -1
  24. package/dist/flow/utils/complex-object-resolver.js +4 -1
  25. package/dist/flow/utils/complex-object-resolver.js.map +1 -1
  26. package/dist/flow/utils/complex-objects.js +1 -1
  27. package/dist/flow/utils/complex-objects.js.map +1 -1
  28. package/dist/flow/utils/flow-constants.d.ts +66 -2
  29. package/dist/flow/utils/flow-constants.js +402 -6
  30. package/dist/flow/utils/flow-constants.js.map +1 -1
  31. package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
  32. package/dist/flow/utils/flow-io-to-record.js +37 -16
  33. package/dist/flow/utils/flow-io-to-record.js.map +1 -1
  34. package/dist/flow/utils/flow-shapes.js +4 -0
  35. package/dist/flow/utils/flow-shapes.js.map +1 -1
  36. package/dist/flow/utils/label-cache-parser.d.ts +9 -2
  37. package/dist/flow/utils/label-cache-parser.js +32 -4
  38. package/dist/flow/utils/label-cache-parser.js.map +1 -1
  39. package/dist/flow/utils/pill-shape-helpers.d.ts +15 -0
  40. package/dist/flow/utils/pill-shape-helpers.js +35 -0
  41. package/dist/flow/utils/pill-shape-helpers.js.map +1 -0
  42. package/dist/flow/utils/pill-string-parser.js +1 -0
  43. package/dist/flow/utils/pill-string-parser.js.map +1 -1
  44. package/dist/flow/utils/schema-to-flow-object.d.ts +6 -1
  45. package/dist/flow/utils/schema-to-flow-object.js +131 -15
  46. package/dist/flow/utils/schema-to-flow-object.js.map +1 -1
  47. package/dist/flow/utils/utils.d.ts +1 -0
  48. package/dist/flow/utils/utils.js +6 -1
  49. package/dist/flow/utils/utils.js.map +1 -1
  50. package/dist/form-plugin.js +7 -9
  51. package/dist/form-plugin.js.map +1 -1
  52. package/dist/inbound-email-action-plugin.d.ts +10 -0
  53. package/dist/inbound-email-action-plugin.js +128 -0
  54. package/dist/inbound-email-action-plugin.js.map +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/instance-scan-plugin.js +0 -5
  59. package/dist/instance-scan-plugin.js.map +1 -1
  60. package/dist/property-plugin.js +1 -1
  61. package/dist/property-plugin.js.map +1 -1
  62. package/dist/record-plugin.d.ts +7 -0
  63. package/dist/record-plugin.js +10 -2
  64. package/dist/record-plugin.js.map +1 -1
  65. package/dist/rest-api-plugin.js +8 -1
  66. package/dist/rest-api-plugin.js.map +1 -1
  67. package/dist/schedule-script/scheduled-script-plugin.js +8 -3
  68. package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
  69. package/dist/service-catalog/service-catalog-base.d.ts +18 -18
  70. package/dist/service-catalog/service-catalog-base.js +22 -22
  71. package/dist/service-catalog/service-catalog-base.js.map +1 -1
  72. package/dist/service-portal/header-footer-plugin.d.ts +2 -0
  73. package/dist/service-portal/header-footer-plugin.js +50 -0
  74. package/dist/service-portal/header-footer-plugin.js.map +1 -0
  75. package/dist/service-portal/menu-plugin.js +3 -22
  76. package/dist/service-portal/menu-plugin.js.map +1 -1
  77. package/dist/service-portal/page-plugin.js +3 -24
  78. package/dist/service-portal/page-plugin.js.map +1 -1
  79. package/dist/service-portal/page-route-map-plugin.d.ts +2 -0
  80. package/dist/service-portal/page-route-map-plugin.js +114 -0
  81. package/dist/service-portal/page-route-map-plugin.js.map +1 -0
  82. package/dist/service-portal/portal-plugin.js +21 -8
  83. package/dist/service-portal/portal-plugin.js.map +1 -1
  84. package/dist/service-portal/utils.d.ts +40 -2
  85. package/dist/service-portal/utils.js +283 -2
  86. package/dist/service-portal/utils.js.map +1 -1
  87. package/dist/service-portal/widget-plugin.js +9 -218
  88. package/dist/service-portal/widget-plugin.js.map +1 -1
  89. package/dist/static-content-plugin.js +4 -0
  90. package/dist/static-content-plugin.js.map +1 -1
  91. package/dist/table-plugin.js +190 -26
  92. package/dist/table-plugin.js.map +1 -1
  93. package/dist/ui-action-plugin.js +1 -4
  94. package/dist/ui-action-plugin.js.map +1 -1
  95. package/dist/ui-page-plugin.js +68 -13
  96. package/dist/ui-page-plugin.js.map +1 -1
  97. package/dist/view-plugin.js +8 -3
  98. package/dist/view-plugin.js.map +1 -1
  99. package/dist/workspace-plugin.js +39 -36
  100. package/dist/workspace-plugin.js.map +1 -1
  101. package/package.json +5 -4
  102. package/src/column-plugin.ts +3 -8
  103. package/src/flow/flow-logic/flow-logic-diagnostics.ts +5 -6
  104. package/src/flow/plugins/flow-action-definition-plugin.ts +1581 -61
  105. package/src/flow/plugins/flow-data-pill-plugin.ts +5 -2
  106. package/src/flow/plugins/flow-definition-plugin.ts +12 -47
  107. package/src/flow/plugins/flow-diagnostics-plugin.ts +2 -2
  108. package/src/flow/plugins/flow-instance-plugin.ts +98 -22
  109. package/src/flow/plugins/step-definition-plugin.ts +2 -1
  110. package/src/flow/plugins/step-instance-plugin.ts +772 -156
  111. package/src/flow/plugins/wfa-datapill-plugin.ts +25 -5
  112. package/src/flow/post-install.ts +1 -0
  113. package/src/flow/utils/complex-object-resolver.ts +4 -1
  114. package/src/flow/utils/complex-objects.ts +1 -1
  115. package/src/flow/utils/flow-constants.ts +421 -5
  116. package/src/flow/utils/flow-io-to-record.ts +43 -17
  117. package/src/flow/utils/flow-shapes.ts +4 -0
  118. package/src/flow/utils/label-cache-parser.ts +33 -4
  119. package/src/flow/utils/pill-shape-helpers.ts +42 -0
  120. package/src/flow/utils/pill-string-parser.ts +1 -0
  121. package/src/flow/utils/schema-to-flow-object.ts +183 -15
  122. package/src/flow/utils/utils.ts +12 -1
  123. package/src/form-plugin.ts +1 -3
  124. package/src/inbound-email-action-plugin.ts +145 -0
  125. package/src/index.ts +4 -0
  126. package/src/instance-scan-plugin.ts +0 -5
  127. package/src/property-plugin.ts +4 -1
  128. package/src/record-plugin.ts +14 -4
  129. package/src/rest-api-plugin.ts +7 -1
  130. package/src/schedule-script/scheduled-script-plugin.ts +14 -3
  131. package/src/service-catalog/service-catalog-base.ts +22 -22
  132. package/src/service-portal/header-footer-plugin.ts +57 -0
  133. package/src/service-portal/menu-plugin.ts +1 -23
  134. package/src/service-portal/page-plugin.ts +3 -28
  135. package/src/service-portal/page-route-map-plugin.ts +124 -0
  136. package/src/service-portal/portal-plugin.ts +33 -10
  137. package/src/service-portal/utils.ts +404 -3
  138. package/src/service-portal/widget-plugin.ts +14 -290
  139. package/src/static-content-plugin.ts +3 -0
  140. package/src/table-plugin.ts +226 -36
  141. package/src/ui-action-plugin.ts +1 -8
  142. package/src/ui-page-plugin.ts +76 -13
  143. package/src/view-plugin.ts +10 -4
  144. package/src/workspace-plugin.ts +43 -43
@@ -1,4 +1,5 @@
1
1
  import type { Logger } from '@servicenow/sdk-build-core'
2
+ import { parseSinglePill } from './pill-string-parser'
2
3
 
3
4
  interface LabelCacheEntry {
4
5
  name: string
@@ -13,10 +14,20 @@ interface LabelCacheEntry {
13
14
  attributes?: Record<string, string>
14
15
  }
15
16
 
17
+ /** Action label cache uses pill category ("action", "step", "static") as the type field */
18
+ const ACTION_PILL_CATEGORIES = new Set(['action', 'step', 'static'])
19
+
16
20
  /**
17
- * Creates a map of name to type from a label_cache string
21
+ * Creates a map of name to type from a label_cache string.
22
+ *
23
+ * Flow/subflow label cache entries use the actual data type in the `type` field and
24
+ * plain names without braces (e.g., name="flow_variable.userName", type="string").
25
+ *
26
+ * Action label cache entries use a pill category in `type` (e.g., "action") and store the
27
+ * actual data type in `base_type`. Names include braces (e.g., "{{action.variable}}").
28
+ *
18
29
  * @param labelCacheString - The stringified JSON array from label_cache
19
- * @returns Map where key is the name field and value is the type field
30
+ * @returns Map where key is the pill name (without braces) and value is the data type
20
31
  */
21
32
  export function createLableCacheNameToTypeMap(labelCacheString: string, logger: Logger): Map<string, string> {
22
33
  const nameToTypeMap = new Map<string, string>()
@@ -25,8 +36,26 @@ export function createLableCacheNameToTypeMap(labelCacheString: string, logger:
25
36
  const entries: LabelCacheEntry[] = JSON.parse(labelCacheString)
26
37
 
27
38
  for (const entry of entries) {
28
- if (entry.name && entry.type) {
29
- nameToTypeMap.set(entry.name, entry.type)
39
+ if (!entry.name) {
40
+ continue
41
+ }
42
+
43
+ if (ACTION_PILL_CATEGORIES.has(entry.type)) {
44
+ // Action label cache: strip braces from name, use base_type for actual data type
45
+ const parsed = parseSinglePill(entry.name, true)
46
+ const key = parsed ? `${parsed.prefix}.${parsed.path}` : entry.name
47
+ nameToTypeMap.set(key, entry.base_type || 'string')
48
+ } else if (entry.type) {
49
+ // Flow/subflow label cache: name is plain, type is the actual data type
50
+ let type = entry.type
51
+ // DEF0792627: The platform sometimes stores 'reference' for the sys_id field
52
+ // when the pill is used in a reference field context (e.g., sysapproval).
53
+ // sys_id is always a string/GUID field never a reference — except for
54
+ // step output Record.sys_id which IS a reference to the record itself.
55
+ if (type === 'reference' && entry.name.endsWith('.sys_id') && !entry.name.includes('.Record.sys_id')) {
56
+ type = 'string'
57
+ }
58
+ nameToTypeMap.set(entry.name, type)
30
59
  }
31
60
  }
32
61
  } catch (error) {
@@ -0,0 +1,42 @@
1
+ import {
2
+ CallExpressionShape,
3
+ type IdentifierShape,
4
+ type PropertyAccessShape,
5
+ StringLiteralShape,
6
+ type Source,
7
+ } from '@servicenow/sdk-build-core'
8
+
9
+ /**
10
+ * Wraps a PropertyAccessShape or IdentifierShape with wfa.dataPill(expression, type).
11
+ * Used by both flow-definition-plugin and flow-action-definition-plugin.
12
+ *
13
+ * @param expression - The property access or identifier to wrap
14
+ * @param source - Source for the shape
15
+ * @param dataType - The data type for the pill
16
+ */
17
+ export function wrapWithDataPillCall(
18
+ expression: PropertyAccessShape | IdentifierShape,
19
+ source: Source,
20
+ dataType = 'string'
21
+ ): CallExpressionShape {
22
+ return new CallExpressionShape({
23
+ source,
24
+ callee: 'wfa.dataPill',
25
+ args: [expression, new StringLiteralShape({ source, literalText: dataType })],
26
+ })
27
+ }
28
+
29
+ /**
30
+ * Extracts all datapill names (without type annotations) from a string containing {{...}} patterns.
31
+ * Used by both flow-definition-plugin and flow-action-definition-plugin.
32
+ */
33
+ export function extractDataPillNames(stringValue: string): string[] {
34
+ const names: string[] = []
35
+ for (const match of stringValue.matchAll(/\{\{([^}]+)\}\}/g)) {
36
+ const pillContent = match[1]?.split('|')[0]
37
+ if (pillContent) {
38
+ names.push(pillContent)
39
+ }
40
+ }
41
+ return names
42
+ }
@@ -121,6 +121,7 @@ export function mapPillPrefixToTsRoot(prefix: string, parameterName: string = 'p
121
121
  inputs: `${parameterName}.inputs`,
122
122
  outputs: `${parameterName}.outputs`, // Schema reference (no property access)
123
123
  subflow: `${parameterName}.inputs`, // ServiceNow uses {{subflow.x}} for subflow inputs
124
+ action: `${parameterName}.inputs`, // ServiceNow uses {{action.x}} for action inputs → params.inputs.x
124
125
 
125
126
  // Record triggers (prefixed with trigger name + _1)
126
127
  Created_1: `${parameterName}.trigger`,
@@ -38,20 +38,129 @@ const SCHEMA_TYPE_TO_COLUMN_API: Record<string, string> = {
38
38
  reference: ReferenceColumn.name,
39
39
  }
40
40
 
41
+ // ------------------------------------------------------------
42
+ // Snapshot value extraction helpers
43
+ // ------------------------------------------------------------
44
+
45
+ /**
46
+ * Recursively walk a snapshot's `complexObjectSchema` and extract all
47
+ * datapill mappings from `mapped` fields inside `$field_facets.SimpleMapFacet`.
48
+ */
49
+ export function extractAllMappedValues(obj: any, result: globalThis.Record<string, string>): void {
50
+ if (typeof obj !== 'object' || obj === null) {
51
+ return
52
+ }
53
+
54
+ if (Array.isArray(obj)) {
55
+ for (const item of obj) {
56
+ extractAllMappedValues(item, result)
57
+ }
58
+ return
59
+ }
60
+
61
+ for (const [key, value] of Object.entries(obj)) {
62
+ if (key.endsWith('.$field_facets')) {
63
+ const facet = (value as any)?.SimpleMapFacet
64
+ if (facet) {
65
+ try {
66
+ const parsed = JSON.parse(facet)
67
+ if (parsed.mapped) {
68
+ const mappedObj = JSON.parse(parsed.mapped)
69
+ for (const [childName, datapill] of Object.entries(mappedObj)) {
70
+ if (typeof datapill === 'string' && datapill) {
71
+ result[childName] = datapill
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // skip unparseable facets
77
+ }
78
+ }
79
+ } else if (typeof value === 'object' && value !== null) {
80
+ extractAllMappedValues(value, result)
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Recursively walk a snapshot's `complexObject` and extract static values
87
+ * from `{ $cv: { $c: "type", $v: "value" } }` structures.
88
+ */
89
+ function extractAllStaticValues(obj: any, result: globalThis.Record<string, string>): void {
90
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
91
+ return
92
+ }
93
+
94
+ for (const [key, value] of Object.entries(obj)) {
95
+ if (key.startsWith('$') || key.endsWith('$')) {
96
+ continue
97
+ }
98
+ if (typeof value === 'object' && value !== null && (value as any).$cv) {
99
+ const staticVal = (value as any).$cv.$v
100
+ if (staticVal !== undefined && staticVal !== '') {
101
+ result[key] = String(staticVal)
102
+ }
103
+ } else if (typeof value === 'object' && value !== null) {
104
+ extractAllStaticValues(value, result)
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Parse a snapshot value JSON for a complex object output and extract
111
+ * per-child value mappings (datapills take priority over static values).
112
+ */
113
+ function parseSnapshotChildValues(snapshotValue: string | undefined): globalThis.Record<string, string> {
114
+ if (!snapshotValue) {
115
+ return {}
116
+ }
117
+
118
+ try {
119
+ const parsed = JSON.parse(snapshotValue)
120
+ const result: globalThis.Record<string, string> = {}
121
+
122
+ // Extract datapill mappings from schema (higher priority)
123
+ const schema = parsed.complexObjectSchema
124
+ if (schema) {
125
+ extractAllMappedValues(schema, result)
126
+ }
127
+
128
+ // Extract static values from complexObject (lower priority — only if no datapill)
129
+ const complexObj = parsed.complexObject
130
+ if (complexObj) {
131
+ const staticValues: globalThis.Record<string, string> = {}
132
+ extractAllStaticValues(complexObj, staticValues)
133
+ for (const [k, v] of Object.entries(staticValues)) {
134
+ if (!result[k]) {
135
+ result[k] = v
136
+ }
137
+ }
138
+ }
139
+
140
+ return result
141
+ } catch {
142
+ return {}
143
+ }
144
+ }
145
+
41
146
  /**
42
147
  * Build a `CallExpressionShape` for a simple (non-object) field given its
43
148
  * schema primitive type and associated facets.
44
149
  */
45
- function buildPrimitiveFieldCallExpr(fieldFacet: ObjectShape, source: FluentRecord): CallExpressionShape {
150
+ function buildPrimitiveFieldCallExpr(
151
+ fieldFacet: ObjectShape,
152
+ source: FluentRecord,
153
+ childValue?: string
154
+ ): CallExpressionShape {
46
155
  const childType = fieldFacet.get('childType').ifDefined()?.asString()?.getValue()
47
156
  const schemaType = childType ?? fieldFacet.get('type')?.asString()?.getValue()
48
157
  const callee = SCHEMA_TYPE_TO_COLUMN_API[schemaType] ?? StringColumn.name
49
158
 
50
159
  const additionalProps: globalThis.Record<string, unknown> = {}
51
160
 
52
- // Extract referenceTable for ReferenceColumn type
161
+ // Extract referenceTable for reference and records column types
53
162
  const referenceTable = fieldFacet.get('reference')?.ifString()?.getValue()
54
- if (referenceTable && schemaType === 'reference') {
163
+ if (referenceTable && (schemaType === 'reference' || schemaType === 'records')) {
55
164
  additionalProps['referenceTable'] = referenceTable
56
165
  }
57
166
 
@@ -99,6 +208,7 @@ function buildPrimitiveFieldCallExpr(fieldFacet: ObjectShape, source: FluentReco
99
208
  })
100
209
  .def(choiceDropdown[ChoiceDropdown.DROPDOWN_WITH_NONE]),
101
210
  }),
211
+ ...(childValue ? { default: $.val(childValue) } : {}),
102
212
  }))
103
213
  .merge(additionalProps),
104
214
  ],
@@ -115,7 +225,8 @@ function buildFlowArrayFromSchema(
115
225
  source: FluentRecord,
116
226
  parentTypeFacet: ObjectShape,
117
227
  complexObjectSysId?: string,
118
- co_type_name?: string
228
+ co_type_name?: string,
229
+ childValues?: globalThis.Record<string, string>
119
230
  ): CallExpressionShape {
120
231
  const rootContent = schemaContent['$COCollectionField']
121
232
  if (!rootContent) {
@@ -129,7 +240,14 @@ function buildFlowArrayFromSchema(
129
240
  let elementType: CallExpressionShape
130
241
  if (type === 'array.object') {
131
242
  const childKey = parentTypeFacet.get('childName')?.asString()?.getValue()
132
- elementType = buildFlowObjectFromSchema(rootContent[0][childKey], source, rootTypeFacet)
243
+ elementType = buildFlowObjectFromSchema(
244
+ rootContent[0][childKey],
245
+ source,
246
+ rootTypeFacet,
247
+ undefined,
248
+ undefined,
249
+ childValues
250
+ )
133
251
  } else {
134
252
  elementType = buildPrimitiveFieldCallExpr(parentTypeFacet, source)
135
253
  }
@@ -163,7 +281,8 @@ function buildFlowArrayFromSchema(
163
281
  function buildNestedFlowArrayFromSchema(
164
282
  schemaContent: Record<string, any>,
165
283
  source: FluentRecord,
166
- parentTypeFacet: ObjectShape
284
+ parentTypeFacet: ObjectShape,
285
+ childValues?: globalThis.Record<string, string>
167
286
  ): CallExpressionShape {
168
287
  const type = parentTypeFacet.get('type')?.asString()?.getValue()
169
288
  let elementType: CallExpressionShape
@@ -175,7 +294,14 @@ function buildNestedFlowArrayFromSchema(
175
294
  const keys = Object.keys(schemaContent)
176
295
  const wrapperKey = keys.find((k) => /^\$\d+_/.test(k)) ?? keys.find((k) => !k.endsWith('.$field_facets'))
177
296
  const innerContent = wrapperKey ? schemaContent[wrapperKey] : schemaContent
178
- elementType = buildFlowObjectFromSchema(innerContent, source, parentTypeFacet)
297
+ elementType = buildFlowObjectFromSchema(
298
+ innerContent,
299
+ source,
300
+ parentTypeFacet,
301
+ undefined,
302
+ undefined,
303
+ childValues
304
+ )
179
305
  } else {
180
306
  elementType = buildPrimitiveFieldCallExpr(parentTypeFacet, source)
181
307
  }
@@ -214,7 +340,9 @@ function buildFlowObjectFromSchema(
214
340
  source: FluentRecord,
215
341
  parentTypeFacet: ObjectShape,
216
342
  complexObjectSysId?: string,
217
- co_type_name?: string
343
+ co_type_name?: string,
344
+ childValues?: globalThis.Record<string, string>,
345
+ topLevelDefault?: string
218
346
  ): CallExpressionShape {
219
347
  const properties: Record<string, unknown> = {}
220
348
 
@@ -230,15 +358,22 @@ function buildFlowObjectFromSchema(
230
358
  throw new Error(`Invalid complex object schema: ${key} facet not found`)
231
359
  }
232
360
  if (typeof value === 'string') {
233
- // Primitive field
234
- properties[key] = buildPrimitiveFieldCallExpr(fieldFacet, source)
361
+ // Primitive field — pass per-child snapshot value if available
362
+ properties[key] = buildPrimitiveFieldCallExpr(fieldFacet, source, childValues?.[key])
235
363
  } else if (typeof value === 'object' && value !== null) {
236
364
  if (Array.isArray(value)) {
237
365
  // Nested FlowArray
238
- properties[key] = buildNestedFlowArrayFromSchema(value[0], source, fieldFacet)
366
+ properties[key] = buildNestedFlowArrayFromSchema(value[0], source, fieldFacet, childValues)
239
367
  } else {
240
368
  // Nested FlowObject
241
- properties[key] = buildFlowObjectFromSchema(value, source, fieldFacet)
369
+ properties[key] = buildFlowObjectFromSchema(
370
+ value,
371
+ source,
372
+ fieldFacet,
373
+ undefined,
374
+ undefined,
375
+ childValues
376
+ )
242
377
  }
243
378
  }
244
379
  }
@@ -256,6 +391,7 @@ function buildFlowObjectFromSchema(
256
391
  .map((label, child) => (child.isDefined() ? child : label))
257
392
  .def(''),
258
393
  mandatory: $.toBoolean().def(false),
394
+ ...(topLevelDefault ? { default: $.val(topLevelDefault) } : {}),
259
395
  })),
260
396
  ],
261
397
  })
@@ -274,7 +410,8 @@ export function buildComplexObjectFromSchema(
274
410
  schema: Record<string, any>,
275
411
  attributes: COAttributes,
276
412
  complexObjectSysId?: string,
277
- co_type_name?: string
413
+ co_type_name?: string,
414
+ snapshotValue?: string
278
415
  ): CallExpressionShape {
279
416
  const { co_type_name: attrCoTypeName, uiType, ...rest } = attributes
280
417
  const rootKey = `FlowDesigner:${attrCoTypeName}`
@@ -292,11 +429,42 @@ export function buildComplexObjectFromSchema(
292
429
  type: uiType,
293
430
  ...rest,
294
431
  })
432
+ // Parse snapshot value into per-child value mappings (datapills and static values)
433
+ const childValues = parseSnapshotChildValues(snapshotValue)
434
+ const hasChildValues = Object.keys(childValues).length > 0 ? childValues : undefined
435
+
436
+ // Detect plain-string snapshot values (e.g. a whole-object datapill like
437
+ // {{step[UUID].__step_status__}}) that are not valid JSON complex-object
438
+ // structures. These map to a top-level `default` on the FlowObject.
439
+ let topLevelDefault: string | undefined
440
+ if (snapshotValue && !hasChildValues) {
441
+ try {
442
+ JSON.parse(snapshotValue)
443
+ } catch {
444
+ topLevelDefault = snapshotValue
445
+ }
446
+ }
447
+
295
448
  // Pass the explicit co_type_name parameter (undefined for deterministic, value for non-deterministic)
296
449
  if (uiType.startsWith('array.')) {
297
- return buildFlowArrayFromSchema(rootContent, source, rootTypeFacet, complexObjectSysId, co_type_name)
450
+ return buildFlowArrayFromSchema(
451
+ rootContent,
452
+ source,
453
+ rootTypeFacet,
454
+ complexObjectSysId,
455
+ co_type_name,
456
+ hasChildValues
457
+ )
298
458
  }
299
- return buildFlowObjectFromSchema(rootContent, source, rootTypeFacet, complexObjectSysId, co_type_name)
459
+ return buildFlowObjectFromSchema(
460
+ rootContent,
461
+ source,
462
+ rootTypeFacet,
463
+ complexObjectSysId,
464
+ co_type_name,
465
+ hasChildValues,
466
+ topLevelDefault
467
+ )
300
468
  }
301
469
 
302
470
  function parseTypeFacet(source: FluentRecord, schema: Record<string, any>, key: string): ObjectShape | undefined {
@@ -20,7 +20,13 @@ import { CallExpressionPlugin } from '../../call-expression-plugin'
20
20
  import { FlowLogicPlugin } from '../flow-logic/flow-logic-plugin'
21
21
  import { FlowInstancePlugin } from '../plugins/flow-instance-plugin'
22
22
  import { FDInstanceShape } from './flow-shapes'
23
- import { CORE_ACTIONS_PREFIX, CORE_ACTIONS_SYS_ID_NAME_MAP, UNSUPPORTED_FLOW_DESCENDANTS } from './flow-constants'
23
+ import {
24
+ CORE_ACTIONS_PREFIX,
25
+ CORE_ACTIONS_SYS_ID_NAME_MAP,
26
+ BUILT_IN_STEP_PREFIX,
27
+ BUILT_IN_STEP_SYS_ID_NAME_MAP,
28
+ UNSUPPORTED_FLOW_DESCENDANTS,
29
+ } from './flow-constants'
24
30
 
25
31
  export function sysIdToUuid(id: string): string {
26
32
  return id.length === 32
@@ -383,6 +389,11 @@ export const getCoreActionIdentifier = (sysId: string) => {
383
389
  return actionName ? `${CORE_ACTIONS_PREFIX}.${actionName}` : undefined
384
390
  }
385
391
 
392
+ export const getBuiltInStepIdentifier = (sysId: string) => {
393
+ const stepName = BUILT_IN_STEP_SYS_ID_NAME_MAP[sysId]
394
+ return stepName ? `${BUILT_IN_STEP_PREFIX}.${stepName}` : undefined
395
+ }
396
+
386
397
  /**
387
398
  * Extracts the order value from a record for sorting purposes.
388
399
  * Converts the 'order' field to a number, defaulting to 0 if not present or invalid.
@@ -15,6 +15,7 @@ import {
15
15
  import { create } from 'xmlbuilder2'
16
16
  import { createSdkDocEntry, showGuidFieldDiagnostic } from './utils'
17
17
  import { NowIdShape } from './now-id-plugin'
18
+ import { AnnotationType, Formatter } from '@servicenow/sdk-core/runtime/ui'
18
19
 
19
20
  const DEFAULT_VIEW = 'Default view'
20
21
  const DEFAULT_ANNOTATION_TYPE = '753f88a80f930000b12e6903cfe01206'
@@ -29,9 +30,6 @@ function buildReverseMap(ns: { [key: string]: string }): Map<string, string> {
29
30
  return map
30
31
  }
31
32
 
32
- //TODO:: Import AnnotationType and Formatter from sdk-api once Form plugin is fixed.
33
- const AnnotationType = {}
34
- const Formatter = {}
35
33
  const ANNOTATION_TYPE_MAP = buildReverseMap(AnnotationType)
36
34
  const FORMATTER_MAP = buildReverseMap(Formatter)
37
35
 
@@ -0,0 +1,145 @@
1
+ import { CallExpressionShape, Plugin, isGUID, type Shape, type ShapeTransform } from '@servicenow/sdk-build-core'
2
+ import { NowIdShape } from './now-id-plugin'
3
+ import { NowIncludeShape } from './now-include-plugin'
4
+ import { ModuleFunctionShape } from './server-module-plugin'
5
+ import { createSdkDocEntry, showGuidFieldDiagnostic } from './utils'
6
+ import { parseString, convertRolesToString } from './service-catalog/utils'
7
+
8
+ /**
9
+ * Plugin for handling Inbound Email Action records (sysevent_in_email_action).
10
+ *
11
+ * Default Behavior:
12
+ * - When reading from XML: If 'action' field is missing or empty, defaults to 'record_action'
13
+ * - When writing to Fluent: The 'action' field is required in the type definition for proper
14
+ * discriminated union type narrowing
15
+ */
16
+ export const InboundEmailActionPlugin = Plugin.create({
17
+ name: 'InboundEmailActionPlugin',
18
+ docs: [createSdkDocEntry('InboundEmailAction', ['sysevent_in_email_action'])],
19
+ records: {
20
+ sysevent_in_email_action: {
21
+ async toShape(record, { transform }) {
22
+ const actionType = record.get('action').getValue() || 'record_action'
23
+
24
+ // Process script
25
+ const script = await NowIncludeShape.fromRecord(record, record.get('script'), transform)
26
+
27
+ return {
28
+ success: true,
29
+ value: new CallExpressionShape({
30
+ source: record,
31
+ callee: 'InboundEmailAction',
32
+ args: [
33
+ record.transform(({ $ }) => {
34
+ const props: Record<string, ShapeTransform | undefined> = {
35
+ $id: $.val(NowIdShape.from(record)),
36
+ name: $,
37
+ description: $.def(''),
38
+ type: $.def('new'),
39
+ action: $.val(actionType),
40
+ active: $.toBoolean().def(false),
41
+ order: $.toNumber().def(100),
42
+ eventName: $.from('event_name').def('email.read'),
43
+ stopProcessing: $.from('stop_processing').toBoolean().def(false),
44
+ conditionScript: $.from('condition_script').def(''),
45
+ filterCondition: $.from('filter_condition').def(''),
46
+ from: $.def(''),
47
+ requiredRoles: $.from('required_roles')
48
+ .map((v: Shape) => parseString(v))
49
+ .def([]),
50
+ }
51
+
52
+ props['replyEmail'] = $.from('reply_email').def('')
53
+ props['table'] = $.def('')
54
+ props['script'] = $.val(script).def('')
55
+ props['fieldAction'] = $.from('template').def('')
56
+ props['assignmentOperator'] = $.from('assignment_operator').def('')
57
+
58
+ return props
59
+ }),
60
+ ],
61
+ }),
62
+ }
63
+ },
64
+ },
65
+ },
66
+ shapes: [
67
+ {
68
+ shape: CallExpressionShape,
69
+ fileTypes: ['fluent'],
70
+ async toRecord(callExpression, { factory, diagnostics }) {
71
+ if (callExpression.getCallee() !== 'InboundEmailAction') {
72
+ return { success: false }
73
+ }
74
+
75
+ const args = callExpression.getArgument(0).asObject()
76
+
77
+ // Get action type to validate field restrictions
78
+ const actionArg = args.get('action')
79
+ const actionType = actionArg?.getValue() ?? 'record_action'
80
+
81
+ // Validate that fieldAction can only be used when table is set (for record_action)
82
+ if (actionType === 'record_action') {
83
+ const tableArg = args.get('table')
84
+ const fieldActionArg = args.get('fieldAction')
85
+ const hasTable = tableArg?.isDefined() && !tableArg.toString().isEmpty()
86
+ const hasFieldAction = fieldActionArg?.isDefined() && !fieldActionArg.toString().isEmpty()
87
+
88
+ if (hasFieldAction && !hasTable) {
89
+ diagnostics.error(
90
+ fieldActionArg,
91
+ `Field 'fieldAction' can only be used when 'table' is specified.`
92
+ )
93
+ }
94
+ }
95
+
96
+ // Validate the 'from' field: must be a GUID string or a Record<'sys_user'>
97
+ const fromArg = args.get('from')
98
+ if (fromArg?.isDefined()) {
99
+ // Validate that if it's a string, it must be a valid GUID
100
+ if (fromArg.isString() && !fromArg.isRecord()) {
101
+ const fromStr = fromArg.asString().getValue()
102
+ if (fromStr !== '' && !isGUID(fromStr)) {
103
+ showGuidFieldDiagnostic(fromArg, 'from', 'sys_user', diagnostics)
104
+ }
105
+ }
106
+ }
107
+
108
+ const record = await factory.createRecord({
109
+ source: callExpression,
110
+ table: 'sysevent_in_email_action',
111
+ explicitId: args.get('$id'),
112
+ properties: args.transform(({ $ }) => ({
113
+ name: $,
114
+ description: $.def(''),
115
+ table: $,
116
+ type: $.def('new'),
117
+ action: $.def('record_action'),
118
+ active: $.toBoolean().def(false),
119
+ order: $.toNumber().def(100),
120
+ event_name: $.from('eventName').def('email.read'),
121
+ stop_processing: $.from('stopProcessing').toBoolean().def(false),
122
+ script: $.map(
123
+ (v: Shape) =>
124
+ v
125
+ .if(ModuleFunctionShape)
126
+ ?.toString(
127
+ (n) => `${n}({{PARAMS}})`,
128
+ ['current', 'event', 'email', 'logger', 'classifier']
129
+ ) ?? v
130
+ ).toCdata(),
131
+ condition_script: $.from('conditionScript').toCdata().def(''),
132
+ filter_condition: $.from('filterCondition').def(''),
133
+ template: $.from('fieldAction').toCdata().def(''),
134
+ from: $.toString(),
135
+ reply_email: $.from('replyEmail').toCdata().def(''),
136
+ required_roles: $.from('requiredRoles').map(convertRolesToString).def(''),
137
+ assignment_operator: $.from('assignmentOperator').def(''),
138
+ })),
139
+ })
140
+
141
+ return { success: true, value: record }
142
+ },
143
+ },
144
+ ],
145
+ })
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export * from './data-plugin'
24
24
  export * from './ui-page-plugin'
25
25
  export * from './list-plugin'
26
26
  export * from './ui-action-plugin'
27
+ export * from './form-plugin'
27
28
  export * from './script-include-plugin'
28
29
  export * from './arrow-function-plugin'
29
30
  export * from './atf/test-plugin'
@@ -34,6 +35,8 @@ export * from './service-portal/portal-plugin'
34
35
  export * from './service-portal/page-plugin'
35
36
  export * from './service-portal/theme-plugin'
36
37
  export * from './service-portal/menu-plugin'
38
+ export * from './service-portal/header-footer-plugin'
39
+ export * from './service-portal/page-route-map-plugin'
37
40
  export * from './rest-api-plugin'
38
41
  export * from './html-import-plugin'
39
42
  export * from './static-content-plugin'
@@ -57,6 +60,7 @@ export * from './import-sets-plugin'
57
60
  export * from './now-attach-plugin'
58
61
  export * from './sla-plugin'
59
62
  export * from './email-notification-plugin'
63
+ export * from './inbound-email-action-plugin'
60
64
 
61
65
  export * from './service-catalog'
62
66
  export * from './ux-list-menu-config-plugin'
@@ -19,11 +19,6 @@ export const InstanceScanPlugin = Plugin.create({
19
19
  ],
20
20
 
21
21
  records: {
22
- scan_check: {
23
- toShape() {
24
- return { success: false }
25
- },
26
- },
27
22
  [SCAN_COLUMN_TYPE_CHECK]: {
28
23
  getUpdateName(record: Record, _context: Context) {
29
24
  return { success: true, value: `${SCAN_COLUMN_TYPE_CHECK}_${record.getId().getValue()}` }
@@ -70,7 +70,10 @@ export const PropertyPlugin = Plugin.create({
70
70
  const name = prop.get('name').asString()
71
71
 
72
72
  if (!isSNScope(scope) && config.scope !== 'global' && !name.asString().startsWith(`${scope}.`)) {
73
- diagnostics.warn(name.getOriginalNode(), `Property name normally begin with '${scope}.'`)
73
+ diagnostics.warn(
74
+ name.getOriginalNode(),
75
+ `Property names should begin with '${scope}.' to match the app scope and avoid collisions with properties from other scopes`
76
+ )
74
77
  }
75
78
 
76
79
  return {