@servicenow/sdk-build-plugins 4.5.0 → 4.6.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/acl-plugin.js +3 -1
- package/dist/acl-plugin.js.map +1 -1
- package/dist/atf/test-plugin.js +6 -8
- package/dist/atf/test-plugin.js.map +1 -1
- package/dist/basic-syntax-plugin.js +10 -3
- package/dist/basic-syntax-plugin.js.map +1 -1
- package/dist/column-plugin.js +99 -49
- package/dist/column-plugin.js.map +1 -1
- package/dist/flow/flow-logic/flow-logic-diagnostics.js +5 -5
- package/dist/flow/flow-logic/flow-logic-diagnostics.js.map +1 -1
- package/dist/flow/plugins/flow-action-definition-plugin.js +1229 -54
- package/dist/flow/plugins/flow-action-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-data-pill-plugin.js +5 -2
- package/dist/flow/plugins/flow-data-pill-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-definition-plugin.js +16 -42
- package/dist/flow/plugins/flow-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/flow-diagnostics-plugin.d.ts +2 -2
- package/dist/flow/plugins/flow-diagnostics-plugin.js +2 -2
- package/dist/flow/plugins/flow-instance-plugin.js +68 -22
- package/dist/flow/plugins/flow-instance-plugin.js.map +1 -1
- package/dist/flow/plugins/step-definition-plugin.js +2 -1
- package/dist/flow/plugins/step-definition-plugin.js.map +1 -1
- package/dist/flow/plugins/step-instance-plugin.d.ts +9 -1
- package/dist/flow/plugins/step-instance-plugin.js +649 -136
- package/dist/flow/plugins/step-instance-plugin.js.map +1 -1
- package/dist/flow/plugins/wfa-datapill-plugin.js +20 -5
- package/dist/flow/plugins/wfa-datapill-plugin.js.map +1 -1
- package/dist/flow/post-install.js +1 -0
- package/dist/flow/post-install.js.map +1 -1
- package/dist/flow/utils/complex-object-resolver.js +4 -1
- package/dist/flow/utils/complex-object-resolver.js.map +1 -1
- package/dist/flow/utils/complex-objects.js +1 -1
- package/dist/flow/utils/complex-objects.js.map +1 -1
- package/dist/flow/utils/flow-constants.d.ts +66 -2
- package/dist/flow/utils/flow-constants.js +402 -6
- package/dist/flow/utils/flow-constants.js.map +1 -1
- package/dist/flow/utils/flow-io-to-record.d.ts +1 -1
- package/dist/flow/utils/flow-io-to-record.js +37 -16
- package/dist/flow/utils/flow-io-to-record.js.map +1 -1
- package/dist/flow/utils/flow-shapes.js +4 -0
- package/dist/flow/utils/flow-shapes.js.map +1 -1
- package/dist/flow/utils/label-cache-parser.d.ts +9 -2
- package/dist/flow/utils/label-cache-parser.js +32 -4
- package/dist/flow/utils/label-cache-parser.js.map +1 -1
- package/dist/flow/utils/pill-shape-helpers.d.ts +15 -0
- package/dist/flow/utils/pill-shape-helpers.js +35 -0
- package/dist/flow/utils/pill-shape-helpers.js.map +1 -0
- package/dist/flow/utils/pill-string-parser.js +1 -0
- package/dist/flow/utils/pill-string-parser.js.map +1 -1
- package/dist/flow/utils/schema-to-flow-object.d.ts +6 -1
- package/dist/flow/utils/schema-to-flow-object.js +131 -15
- package/dist/flow/utils/schema-to-flow-object.js.map +1 -1
- package/dist/flow/utils/utils.d.ts +1 -0
- package/dist/flow/utils/utils.js +6 -1
- package/dist/flow/utils/utils.js.map +1 -1
- package/dist/form-plugin.js +7 -9
- package/dist/form-plugin.js.map +1 -1
- package/dist/inbound-email-action-plugin.d.ts +10 -0
- package/dist/inbound-email-action-plugin.js +128 -0
- package/dist/inbound-email-action-plugin.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-scan-plugin.js +0 -5
- package/dist/instance-scan-plugin.js.map +1 -1
- package/dist/now-config-plugin.js +1 -0
- package/dist/now-config-plugin.js.map +1 -1
- package/dist/property-plugin.js +1 -1
- package/dist/property-plugin.js.map +1 -1
- package/dist/record-plugin.d.ts +7 -0
- package/dist/record-plugin.js +13 -4
- package/dist/record-plugin.js.map +1 -1
- package/dist/rest-api-plugin.js +8 -1
- package/dist/rest-api-plugin.js.map +1 -1
- package/dist/schedule-script/scheduled-script-plugin.js +8 -3
- package/dist/schedule-script/scheduled-script-plugin.js.map +1 -1
- package/dist/script-include-plugin.js +4 -0
- package/dist/script-include-plugin.js.map +1 -1
- package/dist/service-catalog/catalog-clientscript-plugin.js +2 -2
- package/dist/service-catalog/catalog-clientscript-plugin.js.map +1 -1
- package/dist/service-catalog/catalog-ui-policy-plugin.js +2 -2
- package/dist/service-catalog/catalog-ui-policy-plugin.js.map +1 -1
- package/dist/service-catalog/service-catalog-base.d.ts +20 -20
- package/dist/service-catalog/service-catalog-base.js +24 -24
- package/dist/service-catalog/service-catalog-base.js.map +1 -1
- package/dist/service-catalog/utils.js +1 -1
- package/dist/service-catalog/utils.js.map +1 -1
- package/dist/service-portal/header-footer-plugin.d.ts +2 -0
- package/dist/service-portal/header-footer-plugin.js +50 -0
- package/dist/service-portal/header-footer-plugin.js.map +1 -0
- package/dist/service-portal/menu-plugin.js +3 -22
- package/dist/service-portal/menu-plugin.js.map +1 -1
- package/dist/service-portal/page-plugin.js +3 -24
- package/dist/service-portal/page-plugin.js.map +1 -1
- package/dist/service-portal/page-route-map-plugin.d.ts +2 -0
- package/dist/service-portal/page-route-map-plugin.js +114 -0
- package/dist/service-portal/page-route-map-plugin.js.map +1 -0
- package/dist/service-portal/portal-plugin.js +21 -8
- package/dist/service-portal/portal-plugin.js.map +1 -1
- package/dist/service-portal/utils.d.ts +40 -2
- package/dist/service-portal/utils.js +283 -2
- package/dist/service-portal/utils.js.map +1 -1
- package/dist/service-portal/widget-plugin.js +9 -218
- package/dist/service-portal/widget-plugin.js.map +1 -1
- package/dist/static-content-plugin.js +4 -0
- package/dist/static-content-plugin.js.map +1 -1
- package/dist/table-plugin.js +377 -67
- package/dist/table-plugin.js.map +1 -1
- package/dist/ui-action-plugin.js +1 -4
- package/dist/ui-action-plugin.js.map +1 -1
- package/dist/ui-page-plugin.js +68 -13
- package/dist/ui-page-plugin.js.map +1 -1
- package/dist/ui-policy-plugin.js +28 -96
- package/dist/ui-policy-plugin.js.map +1 -1
- package/dist/utils.d.ts +5 -1
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -1
- package/dist/view-plugin.js +8 -3
- package/dist/view-plugin.js.map +1 -1
- package/dist/workspace-plugin.js +39 -36
- package/dist/workspace-plugin.js.map +1 -1
- package/package.json +5 -4
- package/src/acl-plugin.ts +3 -1
- package/src/atf/test-plugin.ts +6 -9
- package/src/basic-syntax-plugin.ts +11 -3
- package/src/column-plugin.ts +137 -75
- package/src/flow/flow-logic/flow-logic-diagnostics.ts +5 -6
- package/src/flow/plugins/flow-action-definition-plugin.ts +1581 -61
- package/src/flow/plugins/flow-data-pill-plugin.ts +5 -2
- package/src/flow/plugins/flow-definition-plugin.ts +12 -47
- package/src/flow/plugins/flow-diagnostics-plugin.ts +2 -2
- package/src/flow/plugins/flow-instance-plugin.ts +98 -22
- package/src/flow/plugins/step-definition-plugin.ts +2 -1
- package/src/flow/plugins/step-instance-plugin.ts +772 -156
- package/src/flow/plugins/wfa-datapill-plugin.ts +25 -5
- package/src/flow/post-install.ts +1 -0
- package/src/flow/utils/complex-object-resolver.ts +4 -1
- package/src/flow/utils/complex-objects.ts +1 -1
- package/src/flow/utils/flow-constants.ts +421 -5
- package/src/flow/utils/flow-io-to-record.ts +43 -17
- package/src/flow/utils/flow-shapes.ts +4 -0
- package/src/flow/utils/label-cache-parser.ts +33 -4
- package/src/flow/utils/pill-shape-helpers.ts +42 -0
- package/src/flow/utils/pill-string-parser.ts +1 -0
- package/src/flow/utils/schema-to-flow-object.ts +183 -15
- package/src/flow/utils/utils.ts +12 -1
- package/src/form-plugin.ts +1 -3
- package/src/inbound-email-action-plugin.ts +145 -0
- package/src/index.ts +4 -0
- package/src/instance-scan-plugin.ts +0 -5
- package/src/now-config-plugin.ts +1 -0
- package/src/property-plugin.ts +4 -1
- package/src/record-plugin.ts +25 -7
- package/src/rest-api-plugin.ts +7 -1
- package/src/schedule-script/scheduled-script-plugin.ts +14 -3
- package/src/script-include-plugin.ts +8 -0
- package/src/service-catalog/catalog-clientscript-plugin.ts +2 -2
- package/src/service-catalog/catalog-ui-policy-plugin.ts +2 -2
- package/src/service-catalog/service-catalog-base.ts +24 -24
- package/src/service-catalog/utils.ts +1 -1
- package/src/service-portal/header-footer-plugin.ts +57 -0
- package/src/service-portal/menu-plugin.ts +1 -23
- package/src/service-portal/page-plugin.ts +3 -28
- package/src/service-portal/page-route-map-plugin.ts +124 -0
- package/src/service-portal/portal-plugin.ts +33 -10
- package/src/service-portal/utils.ts +404 -3
- package/src/service-portal/widget-plugin.ts +14 -290
- package/src/static-content-plugin.ts +3 -0
- package/src/table-plugin.ts +466 -99
- package/src/ui-action-plugin.ts +1 -8
- package/src/ui-page-plugin.ts +76 -13
- package/src/ui-policy-plugin.ts +32 -128
- package/src/utils.ts +52 -0
- package/src/view-plugin.ts +10 -4
- package/src/workspace-plugin.ts +43 -43
package/src/ui-action-plugin.ts
CHANGED
|
@@ -247,13 +247,6 @@ export const UiActionPlugin = Plugin.create({
|
|
|
247
247
|
),
|
|
248
248
|
})),
|
|
249
249
|
})
|
|
250
|
-
const roles = arg.get('roles').ifArray()?.getElements() ?? []
|
|
251
|
-
if (!arg.get('condition').toString().getValue().trim() && roles.length === 0) {
|
|
252
|
-
diagnostics.warn(
|
|
253
|
-
arg.get('roles').ifDefined() ?? arg.get('condition').ifDefined() ?? arg,
|
|
254
|
-
'UI Actions with an empty condition and no roles defined can be called by any logged-in users. Please restrict UI actions. The condition field should be specified to restrict execution of this UI Action to certain users. For example, current.canWrite() condition restricts the UI Action to the users who can modify the current record, gs.hasRole("admin") condition restricts the UI Action to the users with admin role.'
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
250
|
|
|
258
251
|
if (arg.get('script').is(ModuleFunctionShape) && isClient.ifBoolean()?.getValue()) {
|
|
259
252
|
diagnostics.error(isClient, 'Module scripts (sys_module) cannot be used on client-side UI Actions')
|
|
@@ -309,7 +302,7 @@ export const UiActionPlugin = Plugin.create({
|
|
|
309
302
|
})
|
|
310
303
|
)
|
|
311
304
|
}
|
|
312
|
-
|
|
305
|
+
const roles = arg.get('roles').ifArray()?.getElements() ?? []
|
|
313
306
|
const roleRecords: Record[] = []
|
|
314
307
|
for (const role of roles) {
|
|
315
308
|
const roleReference = role.isString()
|
package/src/ui-page-plugin.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type Factory,
|
|
18
18
|
type Transform,
|
|
19
19
|
} from '@servicenow/sdk-build-core'
|
|
20
|
+
import { parseDocument, DomUtils } from 'htmlparser2'
|
|
20
21
|
import { XMLParser, XMLBuilder, type X2jOptions, type XmlBuilderOptions } from 'fast-xml-parser'
|
|
21
22
|
import { create } from 'xmlbuilder2'
|
|
22
23
|
import { NowIdShape } from './now-id-plugin'
|
|
@@ -51,6 +52,62 @@ const builderOptions: XmlBuilderOptions = {
|
|
|
51
52
|
],
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
const LT_PLACEHOLDER = '\0__SDK_LT__\0'
|
|
56
|
+
const AMP_PLACEHOLDER = '\0__SDK_AMP__\0'
|
|
57
|
+
const RAW_CONTENT_TAGS = ['script', 'style', 'textarea'] as const
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Uses htmlparser2 to find script/style/textarea content and replaces `<` and
|
|
61
|
+
* `&` with placeholders. This prevents fast-xml-parser from misinterpreting
|
|
62
|
+
* JavaScript comparison operators (e.g. `a < b`) as XML tag openers, and
|
|
63
|
+
* prevents the XMLBuilder's entity escaping from converting `&` to `$[AMP]`
|
|
64
|
+
* inside script content.
|
|
65
|
+
*
|
|
66
|
+
* htmlparser2 is used instead of regex because it correctly handles edge cases
|
|
67
|
+
* like `>` inside attribute values and script tags inside HTML comments.
|
|
68
|
+
*/
|
|
69
|
+
function escapeRawContent(html: string): string {
|
|
70
|
+
const doc = parseDocument(html, { withStartIndices: true, withEndIndices: true })
|
|
71
|
+
|
|
72
|
+
const regions: { start: number; end: number }[] = []
|
|
73
|
+
for (const tag of RAW_CONTENT_TAGS) {
|
|
74
|
+
for (const el of DomUtils.getElementsByTagName(tag, doc, true)) {
|
|
75
|
+
for (const child of el.children) {
|
|
76
|
+
if (child.type === 'text' && child.startIndex != null && child.endIndex != null) {
|
|
77
|
+
const text = child.data
|
|
78
|
+
if (text.includes('<') || text.includes('&')) {
|
|
79
|
+
regions.push({ start: child.startIndex, end: child.endIndex + 1 })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (regions.length === 0) {
|
|
87
|
+
return html
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Sort by position descending so replacements don't shift earlier indices
|
|
91
|
+
regions.sort((a, b) => b.start - a.start)
|
|
92
|
+
|
|
93
|
+
let result = html
|
|
94
|
+
for (const { start, end } of regions) {
|
|
95
|
+
const content = result.slice(start, end)
|
|
96
|
+
const escaped = content.replace(/&/g, AMP_PLACEHOLDER).replace(/</g, LT_PLACEHOLDER)
|
|
97
|
+
result = result.slice(0, start) + escaped + result.slice(end)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Restores `<` and `&` characters that were replaced by `escapeRawContent`
|
|
105
|
+
* after fast-xml-parser has finished processing.
|
|
106
|
+
*/
|
|
107
|
+
function restoreRawContent(html: string): string {
|
|
108
|
+
return html.replaceAll(LT_PLACEHOLDER, '<').replaceAll(AMP_PLACEHOLDER, '&')
|
|
109
|
+
}
|
|
110
|
+
|
|
54
111
|
const POLARIS_APPSHELL_THEME_ID = 'c86a62e2c7022010099a308dc7c26022'
|
|
55
112
|
const BYOUI_ARTIFACT_NAME_SUFFIX = 'BYOUI Files'
|
|
56
113
|
// Matches the prefix HtmlImportPlugin prepends when it resolves an `import x from '*.html'`
|
|
@@ -388,9 +445,11 @@ export const UiPagePlugin = Plugin.create({
|
|
|
388
445
|
|
|
389
446
|
if (html) {
|
|
390
447
|
try {
|
|
448
|
+
html = escapeRawContent(html)
|
|
391
449
|
const nodes = parser.parse(html)
|
|
392
450
|
const transformed = nodeTransformer(nodes)
|
|
393
451
|
html = new XMLBuilder(builderOptions).build(transformed)
|
|
452
|
+
html = restoreRawContent(html)
|
|
394
453
|
} catch (error: unknown) {
|
|
395
454
|
if (error instanceof Error) {
|
|
396
455
|
diagnostics.error(arg.get('html'), error.message)
|
|
@@ -485,12 +544,14 @@ const getUIPageSourceFilePaths = (
|
|
|
485
544
|
): { files: string[]; assetNames: string[] } => {
|
|
486
545
|
const empty = { files: [], assetNames: [] }
|
|
487
546
|
try {
|
|
488
|
-
// Derive manifest path from HTML path
|
|
489
|
-
//
|
|
490
|
-
// e.g., src/client/index.html
|
|
491
|
-
|
|
547
|
+
// Derive manifest path from HTML path by mirroring the directory structure
|
|
548
|
+
// from clientDir into staticContentDir and swapping the extension.
|
|
549
|
+
// e.g., src/client/index.html -> dist/static/index.ui-source-manifest.json
|
|
550
|
+
// src/client/admin/settings.html -> dist/static/admin/settings.ui-source-manifest.json
|
|
551
|
+
const clientAbsDir = path.join(rootDir, config.clientDir)
|
|
492
552
|
const staticContentAbsDir = path.join(rootDir, config.staticContentDir)
|
|
493
|
-
const
|
|
553
|
+
const htmlRelPath = path.relative(clientAbsDir, htmlFilePath)
|
|
554
|
+
const manifestPath = path.join(staticContentAbsDir, htmlRelPath).replace(/\.html$/, '.ui-source-manifest.json')
|
|
494
555
|
|
|
495
556
|
// Check if manifest file exists
|
|
496
557
|
try {
|
|
@@ -510,24 +571,26 @@ const getUIPageSourceFilePaths = (
|
|
|
510
571
|
|
|
511
572
|
// Derive the JS asset name from the manifest's entry field, matching
|
|
512
573
|
// static-content-plugin's formula: path.join(scope, relativePath_without_ext).
|
|
513
|
-
// The
|
|
514
|
-
//
|
|
515
|
-
//
|
|
574
|
+
// The entry path is relative to the client directory and preserves subdirectories.
|
|
575
|
+
// e.g., src/client/main.tsx -> scope/main
|
|
576
|
+
// src/client/admin/settings.tsx -> scope/admin/settings
|
|
516
577
|
if (!manifest.entry || typeof manifest.entry !== 'string') {
|
|
517
578
|
logger.warn(`No entry field in manifest at ${manifestPath}`)
|
|
518
579
|
return empty
|
|
519
580
|
}
|
|
520
|
-
const
|
|
521
|
-
const
|
|
581
|
+
const entryRelativePath = path.relative(config.clientDir, manifest.entry).replace(/\\/g, '/')
|
|
582
|
+
const entryRelativeWithoutExt = entryRelativePath.replace(/\.[^.]+$/, '')
|
|
583
|
+
const entryAssetName = path.join(config.scope, entryRelativeWithoutExt).replace(/\\/g, '/')
|
|
522
584
|
|
|
523
585
|
// Check if a source map bundle also exists in staticContentDir.
|
|
524
586
|
// static-content-plugin names source map assets as: path.join(scope, relativePath.replace('dbx', ''))
|
|
525
|
-
// e.g. main.jsdbx.map ->
|
|
587
|
+
// e.g. main.jsdbx.map -> scope/main.js.map
|
|
588
|
+
// admin/settings.jsdbx.map -> scope/admin/settings.js.map
|
|
526
589
|
const assetNames = [entryAssetName]
|
|
527
|
-
const sourceMapFilePath = path.join(staticContentAbsDir, `${
|
|
590
|
+
const sourceMapFilePath = path.join(staticContentAbsDir, `${entryRelativeWithoutExt}.jsdbx.map`)
|
|
528
591
|
try {
|
|
529
592
|
fs.accessSync(sourceMapFilePath)
|
|
530
|
-
const sourceMapAssetName = path.join(config.scope, `${
|
|
593
|
+
const sourceMapAssetName = path.join(config.scope, `${entryRelativeWithoutExt}.js.map`).replace(/\\/g, '/')
|
|
531
594
|
assetNames.push(sourceMapAssetName)
|
|
532
595
|
} catch {
|
|
533
596
|
// no source map in this build output — skip
|
package/src/ui-policy-plugin.ts
CHANGED
|
@@ -108,128 +108,35 @@ export const UiPolicyPlugin = Plugin.create({
|
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
110
|
toShape(record, { descendants }) {
|
|
111
|
-
const actions = descendants
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
fieldMessage?: string
|
|
128
|
-
fieldMessageType?: string
|
|
129
|
-
valueAction?: string
|
|
130
|
-
} = {
|
|
131
|
-
field: fieldValue,
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Convert ServiceNow string format to boolean | 'ignore'
|
|
135
|
-
const visible = stringToBoolean(action.get('visible')?.ifString()?.getValue())
|
|
136
|
-
const disabled = stringToBoolean(action.get('disabled')?.ifString()?.getValue())
|
|
137
|
-
const mandatory = stringToBoolean(action.get('mandatory')?.ifString()?.getValue())
|
|
138
|
-
|
|
139
|
-
// Only include visible if it's not 'ignore'
|
|
140
|
-
if (visible !== undefined && visible !== 'ignore') {
|
|
141
|
-
actionObj.visible = visible
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Convert 'disabled' to 'readOnly'
|
|
145
|
-
// Only include 'readOnly' if it's not 'ignore'
|
|
146
|
-
if (disabled !== undefined && disabled !== 'ignore') {
|
|
147
|
-
actionObj.readOnly = disabled
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Only include mandatory if it's not 'ignore'
|
|
151
|
-
if (mandatory !== undefined && mandatory !== 'ignore') {
|
|
152
|
-
actionObj.mandatory = mandatory
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const cleared = action.get('cleared')?.ifBoolean()?.getValue()
|
|
156
|
-
if (cleared) {
|
|
157
|
-
actionObj.cleared = cleared
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Add new optional fields - only if they have meaningful values
|
|
161
|
-
const table = action.get('table')?.ifString()?.getValue()
|
|
162
|
-
const parentTable = record.get('table')?.ifString()?.getValue()
|
|
163
|
-
// Only include table if it's different from the parent policy's table
|
|
164
|
-
if (table && table !== parentTable) {
|
|
165
|
-
actionObj.table = table
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const value = action.get('value')?.ifString()?.getValue()
|
|
169
|
-
if (value) {
|
|
170
|
-
actionObj.value = value
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const fieldMessage = action.get('field_message')?.ifString()?.getValue()
|
|
174
|
-
if (fieldMessage) {
|
|
175
|
-
actionObj.fieldMessage = fieldMessage
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const fieldMessageType = action.get('field_message_type')?.ifString()?.getValue()
|
|
179
|
-
// Only include if it's not the default value 'none'
|
|
180
|
-
if (fieldMessageType && fieldMessageType !== 'none') {
|
|
181
|
-
actionObj.fieldMessageType = fieldMessageType
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const valueAction = action.get('value_action')?.ifString()?.getValue()
|
|
185
|
-
// Only include if it's not the default value 'ignore'
|
|
186
|
-
if (valueAction && valueAction !== 'ignore') {
|
|
187
|
-
actionObj.valueAction = valueAction
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Skip actions that have no meaningful properties (all are 'ignore')
|
|
191
|
-
if (Object.keys(actionObj).length === 1) {
|
|
192
|
-
return null // Only 'field' property exists, skip this action
|
|
193
|
-
}
|
|
111
|
+
const actions = descendants.query(SYS_UI_POLICY_ACTION).map((action) => {
|
|
112
|
+
return action.transform(({ $ }) => ({
|
|
113
|
+
field: $,
|
|
114
|
+
visible: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
|
|
115
|
+
readOnly: $.from('disabled')
|
|
116
|
+
.map((v) => stringToBoolean(v?.asString()?.getValue()))
|
|
117
|
+
.def('ignore'),
|
|
118
|
+
mandatory: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
|
|
119
|
+
cleared: $.toBoolean().def(false),
|
|
120
|
+
table: $.def(''),
|
|
121
|
+
value: $.def(''),
|
|
122
|
+
fieldMessage: $.from('field_message').def(''),
|
|
123
|
+
fieldMessageType: $.from('field_message_type').def('none'),
|
|
124
|
+
valueAction: $.from('value_action').def('ignore'),
|
|
125
|
+
}))
|
|
126
|
+
})
|
|
194
127
|
|
|
195
|
-
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
.query(SYS_UI_POLICY_RL_ACTION)
|
|
202
|
-
.map((rlAction) => {
|
|
203
|
-
const rlActionObj: {
|
|
204
|
-
list?: string
|
|
205
|
-
visible?: boolean | 'ignore'
|
|
206
|
-
} = {}
|
|
207
|
-
|
|
208
|
-
const listValue = rlAction.get('list')?.ifString()?.getValue()
|
|
209
|
-
if (listValue) {
|
|
210
|
-
// Strip REL: prefix if present (transform ServiceNow → Fluent)
|
|
211
|
-
// Users write plain GUIDs or table.field format in Fluent code
|
|
212
|
-
if (listValue.startsWith('REL:')) {
|
|
213
|
-
rlActionObj.list = listValue.substring(4) // Remove 'REL:' prefix
|
|
214
|
-
} else {
|
|
215
|
-
rlActionObj.list = listValue
|
|
128
|
+
const relatedListActions = descendants.query(SYS_UI_POLICY_RL_ACTION).map((rlAction) =>
|
|
129
|
+
rlAction.transform(({ $ }) => ({
|
|
130
|
+
list: $.map((v) => {
|
|
131
|
+
if (v?.ifString()?.ifDefined()) {
|
|
132
|
+
const listVal = v.asString().getValue()
|
|
133
|
+
return listVal.startsWith('REL:') ? listVal.substring(4) : listVal
|
|
216
134
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
rlActionObj.visible = visible
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Skip if no meaningful properties (only $id exists)
|
|
226
|
-
if (Object.keys(rlActionObj).length === 0) {
|
|
227
|
-
return null
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return rlActionObj
|
|
231
|
-
})
|
|
232
|
-
.filter((rlAction) => rlAction !== null) // Remove null actions
|
|
135
|
+
return ''
|
|
136
|
+
}).def(''),
|
|
137
|
+
visible: $.map((v) => stringToBoolean(v?.asString()?.getValue())).def('ignore'),
|
|
138
|
+
}))
|
|
139
|
+
)
|
|
233
140
|
|
|
234
141
|
return {
|
|
235
142
|
success: true,
|
|
@@ -278,7 +185,7 @@ export const UiPolicyPlugin = Plugin.create({
|
|
|
278
185
|
order: $.from('order').toNumber().def(100),
|
|
279
186
|
setValues: $.from('set_values').def(''),
|
|
280
187
|
view: $.from('view').def(''),
|
|
281
|
-
actions: $.val(actions.
|
|
188
|
+
actions: $.val(actions).def([]),
|
|
282
189
|
relatedListActions: $.val(
|
|
283
190
|
relatedListActions.length > 0 ? relatedListActions : undefined
|
|
284
191
|
),
|
|
@@ -401,11 +308,10 @@ export const UiPolicyPlugin = Plugin.create({
|
|
|
401
308
|
const hasClearedProp = action.get('cleared')?.isBoolean() || false
|
|
402
309
|
|
|
403
310
|
if (!hasVisibleProp && !hasReadOnlyProp && !hasMandatoryProp && !hasClearedProp) {
|
|
404
|
-
diagnostics.
|
|
311
|
+
diagnostics.hint(
|
|
405
312
|
action,
|
|
406
|
-
`Action at index ${i}
|
|
313
|
+
`Action at index ${i} has no effect — consider specifying at least one of: visible, readOnly, mandatory, or cleared`
|
|
407
314
|
)
|
|
408
|
-
continue
|
|
409
315
|
}
|
|
410
316
|
|
|
411
317
|
const actionRecord = await factory.createRecord({
|
|
@@ -475,14 +381,12 @@ export const UiPolicyPlugin = Plugin.create({
|
|
|
475
381
|
}
|
|
476
382
|
}
|
|
477
383
|
|
|
478
|
-
// At least one property must be specified (visible or list)
|
|
479
384
|
const hasVisibleProp = isValidActionValue(rlAction.get('visible'))
|
|
480
385
|
if (!hasVisibleProp && !listValue) {
|
|
481
|
-
diagnostics.
|
|
386
|
+
diagnostics.hint(
|
|
482
387
|
rlAction,
|
|
483
|
-
`Related list action at index ${i}
|
|
388
|
+
`Related list action at index ${i} has no effect — consider specifying at least one of: list or visible`
|
|
484
389
|
)
|
|
485
|
-
continue
|
|
486
390
|
}
|
|
487
391
|
|
|
488
392
|
const rlActionRecord = await factory.createRecord({
|
package/src/utils.ts
CHANGED
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
type Compiler,
|
|
7
7
|
type PluginApiDoc,
|
|
8
8
|
type Record as FluentRecord,
|
|
9
|
+
type OutputFile,
|
|
10
|
+
type Transform,
|
|
9
11
|
} from '@servicenow/sdk-build-core'
|
|
12
|
+
import { create } from 'xmlbuilder2'
|
|
10
13
|
|
|
11
14
|
export function toReference(shape: Shape) {
|
|
12
15
|
return shape.ifRecord()?.getId() ?? shape.ifString()?.getValue() ?? ''
|
|
@@ -130,6 +133,55 @@ export const showGuidFieldDiagnostic = (
|
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
export async function generateChoiceSetFile(
|
|
137
|
+
choiceSet: FluentRecord,
|
|
138
|
+
choices: FluentRecord[],
|
|
139
|
+
config: { scope: string; scopeId: string },
|
|
140
|
+
transform: Transform
|
|
141
|
+
): Promise<OutputFile> {
|
|
142
|
+
const tableName = choiceSet.get('name').asString().getValue()
|
|
143
|
+
const elementName = choiceSet.get('element').asString().getValue()
|
|
144
|
+
const xml = create().ele('record_update')
|
|
145
|
+
const root = xml.ele('sys_choice_set', { table: tableName, field: elementName })
|
|
146
|
+
|
|
147
|
+
choices.forEach((choice) => {
|
|
148
|
+
const child = root.ele('sys_choice', { action: choice.getAction() })
|
|
149
|
+
child.ele('sys_id').txt(choice.getId().getValue())
|
|
150
|
+
child.ele('name').txt(tableName)
|
|
151
|
+
child.ele('element').txt(elementName)
|
|
152
|
+
|
|
153
|
+
for (const prop of [
|
|
154
|
+
'label',
|
|
155
|
+
'value',
|
|
156
|
+
'sequence',
|
|
157
|
+
'dependent_value',
|
|
158
|
+
'hint',
|
|
159
|
+
'inactive',
|
|
160
|
+
'inactive_on_update',
|
|
161
|
+
'language',
|
|
162
|
+
]) {
|
|
163
|
+
choice
|
|
164
|
+
.get(prop)
|
|
165
|
+
.ifDefined()
|
|
166
|
+
?.toString()
|
|
167
|
+
.pipe((p) => child.ele(prop).txt(p.getValue()))
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const child = root.ele('sys_choice_set', { action: choiceSet.getAction() })
|
|
172
|
+
child.ele('sys_id').txt(choiceSet.getId().getValue())
|
|
173
|
+
child.ele('sys_scope', { display_value: config.scope }).txt(config.scopeId)
|
|
174
|
+
child.ele('name').txt(tableName)
|
|
175
|
+
child.ele('element').txt(elementName)
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
source: choiceSet,
|
|
179
|
+
name: `${await transform.getUpdateName(choiceSet)}.xml`,
|
|
180
|
+
category: choiceSet.getInstallCategory(),
|
|
181
|
+
content: xml.end({ prettyPrint: true }),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
133
185
|
/**
|
|
134
186
|
* Creates a documentation entry for first-party SDK plugins.
|
|
135
187
|
*
|
package/src/view-plugin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plugin, Record, RecordId, Shape } from '@servicenow/sdk-build-core'
|
|
1
|
+
import { Plugin, Record, RecordId, Shape, ts } from '@servicenow/sdk-build-core'
|
|
2
2
|
|
|
3
3
|
export const ViewPlugin = Plugin.create({
|
|
4
4
|
name: 'ViewPlugin',
|
|
@@ -11,7 +11,7 @@ export const ViewPlugin = Plugin.create({
|
|
|
11
11
|
shapes: [
|
|
12
12
|
{
|
|
13
13
|
shape: Record,
|
|
14
|
-
inspect(record, { diagnostics }) {
|
|
14
|
+
inspect(record, { diagnostics, logger }) {
|
|
15
15
|
if (record.getTable() !== 'sys_ui_view') {
|
|
16
16
|
return
|
|
17
17
|
}
|
|
@@ -22,8 +22,14 @@ export const ViewPlugin = Plugin.create({
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const viewName = record.get('name')
|
|
25
|
-
if (!viewName.isString() || !/^[a-zA-Z0-9_]+$/.test(viewName.getValue())) {
|
|
26
|
-
|
|
25
|
+
if (!viewName.isString() || !/^[a-zA-Z0-9_,]+$/.test(viewName.getValue())) {
|
|
26
|
+
if (ts.Node.isNode(viewName.getOriginalSource())) {
|
|
27
|
+
diagnostics.error(viewName, `View name can only contain alphanumeric characters`)
|
|
28
|
+
} else {
|
|
29
|
+
logger.warn(
|
|
30
|
+
`[ViewPlugin] View name '${viewName.isString() ? viewName.getValue() : ''}' in ${record.getOriginalFilePath()} can only contain alphanumeric characters`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
},
|
|
29
35
|
},
|
package/src/workspace-plugin.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Record,
|
|
6
6
|
type Shape,
|
|
7
7
|
type ObjectShape,
|
|
8
|
+
Database,
|
|
8
9
|
} from '@servicenow/sdk-build-core'
|
|
9
10
|
import { NowIdShape } from './now-id-plugin'
|
|
10
11
|
import { createSdkDocEntry } from './utils'
|
|
@@ -47,68 +48,36 @@ export const WorkspacePlugin = Plugin.create({
|
|
|
47
48
|
sys_ux_page_registry: {
|
|
48
49
|
relationships: {
|
|
49
50
|
sys_ux_registry_m2m_category: {
|
|
50
|
-
via:
|
|
51
|
-
child.getCreator()?.getName() === 'WorkspacePlugin' &&
|
|
52
|
-
parent.getId().getValue() ===
|
|
53
|
-
(child.get('page_registry').isRecord()
|
|
54
|
-
? child.get('page_registry').asRecord().getId().getValue()
|
|
55
|
-
: child.get('page_registry').asString().getValue()),
|
|
51
|
+
via: 'page_registry',
|
|
56
52
|
descendant: true,
|
|
57
53
|
},
|
|
58
54
|
sys_ux_page_property: {
|
|
59
|
-
via:
|
|
60
|
-
child.getCreator()?.getName() === 'WorkspacePlugin' &&
|
|
61
|
-
parent.getId().getValue() ===
|
|
62
|
-
(child.get('page').isRecord()
|
|
63
|
-
? child.get('page').asRecord().getId().getValue()
|
|
64
|
-
: child.get('page').asString().getValue()),
|
|
55
|
+
via: 'page',
|
|
65
56
|
descendant: true,
|
|
66
57
|
},
|
|
67
58
|
sys_ux_app_config: {
|
|
68
|
-
via:
|
|
69
|
-
|
|
70
|
-
child.getId().getValue() ===
|
|
71
|
-
(parent.get('admin_panel').isRecord()
|
|
72
|
-
? parent.get('admin_panel').asRecord().getId().getValue()
|
|
73
|
-
: parent.get('admin_panel').asString().getValue()),
|
|
59
|
+
via: 'admin_panel',
|
|
60
|
+
inverse: true,
|
|
74
61
|
descendant: true,
|
|
75
62
|
relationships: {
|
|
76
63
|
sys_ux_screen: {
|
|
77
|
-
via:
|
|
78
|
-
child.getCreator()?.getName() === 'WorkspacePlugin' &&
|
|
79
|
-
parent.getId().getValue() ===
|
|
80
|
-
(child.get('app_config').isRecord()
|
|
81
|
-
? child.get('app_config').asRecord().getId().getValue()
|
|
82
|
-
: child.get('app_config').asString().getValue()),
|
|
64
|
+
via: 'app_config',
|
|
83
65
|
descendant: true,
|
|
84
66
|
relationships: {
|
|
85
67
|
sys_ux_macroponent: {
|
|
86
|
-
via:
|
|
87
|
-
|
|
88
|
-
child.getId().getValue() ===
|
|
89
|
-
(parent.get('macroponent').isRecord()
|
|
90
|
-
? parent.get('macroponent').asRecord().getId().getValue()
|
|
91
|
-
: parent.get('macroponent').asString().getValue()),
|
|
68
|
+
via: 'macroponent',
|
|
69
|
+
inverse: true,
|
|
92
70
|
descendant: true,
|
|
93
71
|
},
|
|
94
72
|
},
|
|
95
73
|
},
|
|
96
74
|
sys_ux_app_route: {
|
|
97
|
-
via:
|
|
98
|
-
child.getCreator()?.getName() === 'WorkspacePlugin' &&
|
|
99
|
-
parent.getId().getValue() ===
|
|
100
|
-
(child.get('app_config').isRecord()
|
|
101
|
-
? child.get('app_config').asRecord().getId().getValue()
|
|
102
|
-
: child.get('app_config').asString().getValue()),
|
|
75
|
+
via: 'app_config',
|
|
103
76
|
descendant: true,
|
|
104
77
|
relationships: {
|
|
105
78
|
sys_ux_screen_type: {
|
|
106
|
-
via:
|
|
107
|
-
|
|
108
|
-
child.getId().getValue() ===
|
|
109
|
-
(parent.get('screen_type').isRecord()
|
|
110
|
-
? parent.get('screen_type').asRecord().getId().getValue()
|
|
111
|
-
: parent.get('screen_type').asString().getValue()),
|
|
79
|
+
via: 'screen_type',
|
|
80
|
+
inverse: true,
|
|
112
81
|
descendant: true,
|
|
113
82
|
},
|
|
114
83
|
},
|
|
@@ -116,10 +85,33 @@ export const WorkspacePlugin = Plugin.create({
|
|
|
116
85
|
},
|
|
117
86
|
},
|
|
118
87
|
},
|
|
119
|
-
async toShape(pageRegRecord, { factory, descendants }) {
|
|
88
|
+
async toShape(pageRegRecord, { factory, descendants: descendantsDB }) {
|
|
120
89
|
if (pageRegRecord.getCreator()?.getName() !== 'WorkspacePlugin') {
|
|
121
90
|
return { success: false }
|
|
122
91
|
}
|
|
92
|
+
|
|
93
|
+
const excludedRecords: Record[] = []
|
|
94
|
+
const supportedRecords: Record[] = []
|
|
95
|
+
const partiallySupportedTables = new Set<string>([
|
|
96
|
+
'sys_ux_registry_m2m_category',
|
|
97
|
+
'sys_ux_page_property',
|
|
98
|
+
'sys_ux_app_config',
|
|
99
|
+
'sys_ux_screen',
|
|
100
|
+
'sys_ux_macroponent',
|
|
101
|
+
'sys_ux_app_route',
|
|
102
|
+
'sys_ux_screen_type',
|
|
103
|
+
])
|
|
104
|
+
descendantsDB.query().forEach((descendant) => {
|
|
105
|
+
if (
|
|
106
|
+
partiallySupportedTables.has(descendant.getTable()) &&
|
|
107
|
+
descendant.getCreator()?.getName() !== 'WorkspacePlugin'
|
|
108
|
+
) {
|
|
109
|
+
excludedRecords.push(descendant)
|
|
110
|
+
} else {
|
|
111
|
+
supportedRecords.push(descendant)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
const descendants = new Database(supportedRecords)
|
|
123
115
|
// Get 'path' and 'title'
|
|
124
116
|
// TODO 'active'
|
|
125
117
|
const path: string = pageRegRecord.get('path').asString().getValue() ?? ''
|
|
@@ -226,6 +218,14 @@ export const WorkspacePlugin = Plugin.create({
|
|
|
226
218
|
defaultRecordOverrides,
|
|
227
219
|
})
|
|
228
220
|
|
|
221
|
+
if (excludedRecords.length > 0) {
|
|
222
|
+
return {
|
|
223
|
+
success: 'partial',
|
|
224
|
+
value: callExpressionShape,
|
|
225
|
+
unhandledRecords: excludedRecords,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
229
|
return {
|
|
230
230
|
success: true,
|
|
231
231
|
value: callExpressionShape,
|