@servicenow/sdk-build-plugins 2.0.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/AttachmentPlugin.d.ts +253 -0
- package/dist/AttachmentPlugin.js +216 -0
- package/dist/AttachmentPlugin.js.map +1 -0
- package/dist/BusinessRulePlugin.d.ts +56 -0
- package/dist/BusinessRulePlugin.js +171 -0
- package/dist/BusinessRulePlugin.js.map +1 -0
- package/dist/CrossScopePrivilegePlugin.d.ts +22 -0
- package/dist/CrossScopePrivilegePlugin.js +42 -0
- package/dist/CrossScopePrivilegePlugin.js.map +1 -0
- package/dist/DefaultPlugin.d.ts +71 -0
- package/dist/DefaultPlugin.js +238 -0
- package/dist/DefaultPlugin.js.map +1 -0
- package/dist/IdPlugin.d.ts +17 -0
- package/dist/IdPlugin.js +45 -0
- package/dist/IdPlugin.js.map +1 -0
- package/dist/ListPlugin.d.ts +91 -0
- package/dist/ListPlugin.js +398 -0
- package/dist/ListPlugin.js.map +1 -0
- package/dist/PropertyPlugin.d.ts +122 -0
- package/dist/PropertyPlugin.js +165 -0
- package/dist/PropertyPlugin.js.map +1 -0
- package/dist/ScriptTemplatePlugin.d.ts +31 -0
- package/dist/ScriptTemplatePlugin.js +208 -0
- package/dist/ScriptTemplatePlugin.js.map +1 -0
- package/dist/UserPreferencePlugin.d.ts +16 -0
- package/dist/UserPreferencePlugin.js +30 -0
- package/dist/UserPreferencePlugin.js.map +1 -0
- package/dist/aclAndRole/AclPlugin.d.ts +117 -0
- package/dist/aclAndRole/AclPlugin.js +285 -0
- package/dist/aclAndRole/AclPlugin.js.map +1 -0
- package/dist/aclAndRole/RolePlugin.d.ts +58 -0
- package/dist/aclAndRole/RolePlugin.js +152 -0
- package/dist/aclAndRole/RolePlugin.js.map +1 -0
- package/dist/aclAndRole/Util.d.ts +3 -0
- package/dist/aclAndRole/Util.js +106 -0
- package/dist/aclAndRole/Util.js.map +1 -0
- package/dist/app/ApplicationMenuPlugin.d.ts +32 -0
- package/dist/app/ApplicationMenuPlugin.js +106 -0
- package/dist/app/ApplicationMenuPlugin.js.map +1 -0
- package/dist/atf/ATFComposer.d.ts +492 -0
- package/dist/atf/ATFComposer.js +2717 -0
- package/dist/atf/ATFComposer.js.map +1 -0
- package/dist/atf/TestPlugin.d.ts +31 -0
- package/dist/atf/TestPlugin.js +95 -0
- package/dist/atf/TestPlugin.js.map +1 -0
- package/dist/atf/index.d.ts +1 -0
- package/dist/atf/index.js +9 -0
- package/dist/atf/index.js.map +1 -0
- package/dist/db/ColumnPlugins.d.ts +278 -0
- package/dist/db/ColumnPlugins.js +112 -0
- package/dist/db/ColumnPlugins.js.map +1 -0
- package/dist/db/RecordPlugin.d.ts +208 -0
- package/dist/db/RecordPlugin.js +287 -0
- package/dist/db/RecordPlugin.js.map +1 -0
- package/dist/db/TablePlugin.d.ts +742 -0
- package/dist/db/TablePlugin.js +1249 -0
- package/dist/db/TablePlugin.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +27 -0
- package/dist/db/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.d.ts +12 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +371 -0
- package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.d.ts +15 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.js +177 -0
- package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +144 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.js +318 -0
- package/dist/scriptedRESTAPI/RestApiPlugin.js.map +1 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.d.ts +190 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.js +53 -0
- package/dist/scriptedRESTAPI/RestSchemaUtils.js.map +1 -0
- package/dist/scriptedRESTAPI/RestUtils.d.ts +75 -0
- package/dist/scriptedRESTAPI/RestUtils.js +469 -0
- package/dist/scriptedRESTAPI/RestUtils.js.map +1 -0
- package/dist/scripts/ClientScriptPlugin.d.ts +43 -0
- package/dist/scripts/ClientScriptPlugin.js +190 -0
- package/dist/scripts/ClientScriptPlugin.js.map +1 -0
- package/dist/scripts/scriptUtils.d.ts +15 -0
- package/dist/scripts/scriptUtils.js +83 -0
- package/dist/scripts/scriptUtils.js.map +1 -0
- package/dist/uxf/ExperiencePlugin.d.ts +22 -0
- package/dist/uxf/ExperiencePlugin.js +55 -0
- package/dist/uxf/ExperiencePlugin.js.map +1 -0
- package/dist/uxf/RoutesPlugin.d.ts +22 -0
- package/dist/uxf/RoutesPlugin.js +176 -0
- package/dist/uxf/RoutesPlugin.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.d.ts +4 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.js +65 -0
- package/dist/uxf/UxfFormulaParser/cleanUxValue.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.d.ts +189 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.js +158 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/api.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.d.ts +13 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +604 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.d.ts +12 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.d.ts +31 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +64 -0
- package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/index.d.ts +3 -0
- package/dist/uxf/UxfFormulaParser/index.js +11 -0
- package/dist/uxf/UxfFormulaParser/index.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/parser.d.ts +8 -0
- package/dist/uxf/UxfFormulaParser/parser.js +87 -0
- package/dist/uxf/UxfFormulaParser/parser.js.map +1 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.d.ts +8 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js +17 -0
- package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js.map +1 -0
- package/dist/uxf/constants.d.ts +2 -0
- package/dist/uxf/constants.js +8 -0
- package/dist/uxf/constants.js.map +1 -0
- package/dist/uxf/index.d.ts +2 -0
- package/dist/uxf/index.js +11 -0
- package/dist/uxf/index.js.map +1 -0
- package/dist/uxf/tectonicIdGenerator.d.ts +12 -0
- package/dist/uxf/tectonicIdGenerator.js +102 -0
- package/dist/uxf/tectonicIdGenerator.js.map +1 -0
- package/license +9 -0
- package/package.json +42 -0
- package/src/AttachmentPlugin.ts +262 -0
- package/src/BusinessRulePlugin.ts +251 -0
- package/src/CrossScopePrivilegePlugin.ts +54 -0
- package/src/DefaultPlugin.ts +272 -0
- package/src/IdPlugin.ts +47 -0
- package/src/ListPlugin.ts +497 -0
- package/src/PropertyPlugin.ts +218 -0
- package/src/ScriptTemplatePlugin.ts +223 -0
- package/src/UserPreferencePlugin.ts +36 -0
- package/src/aclAndRole/AclPlugin.ts +410 -0
- package/src/aclAndRole/RolePlugin.ts +225 -0
- package/src/aclAndRole/Util.ts +104 -0
- package/src/app/ApplicationMenuPlugin.ts +158 -0
- package/src/atf/ATFComposer.ts +3356 -0
- package/src/atf/TestPlugin.ts +119 -0
- package/src/atf/index.ts +1 -0
- package/src/db/ColumnPlugins.ts +117 -0
- package/src/db/RecordPlugin.ts +391 -0
- package/src/db/TablePlugin.ts +1581 -0
- package/src/db/index.ts +3 -0
- package/src/index.ts +16 -0
- package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +410 -0
- package/src/scriptedRESTAPI/RESTSerializationUtils.ts +227 -0
- package/src/scriptedRESTAPI/RestApiPlugin.ts +438 -0
- package/src/scriptedRESTAPI/RestSchemaUtils.ts +72 -0
- package/src/scriptedRESTAPI/RestUtils.ts +507 -0
- package/src/scripts/ClientScriptPlugin.ts +251 -0
- package/src/scripts/scriptUtils.ts +81 -0
- package/src/uxf/ExperiencePlugin.ts +64 -0
- package/src/uxf/RoutesPlugin.ts +215 -0
- package/src/uxf/UxfFormulaParser/cleanUxValue.ts +73 -0
- package/src/uxf/UxfFormulaParser/grammerParser/api.js +166 -0
- package/src/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +606 -0
- package/src/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
- package/src/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +65 -0
- package/src/uxf/UxfFormulaParser/index.ts +4 -0
- package/src/uxf/UxfFormulaParser/parser.ts +64 -0
- package/src/uxf/UxfFormulaParser/utils/getErrorMsg.ts +13 -0
- package/src/uxf/constants.ts +4 -0
- package/src/uxf/index.ts +2 -0
- package/src/uxf/tectonicIdGenerator.ts +81 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { ClientScript } from '@servicenow/sdk-core/runtime/clientscript'
|
|
2
|
+
import {
|
|
3
|
+
Context,
|
|
4
|
+
Plugin,
|
|
5
|
+
extractCallExpression,
|
|
6
|
+
getCallExpressionName,
|
|
7
|
+
removeNode,
|
|
8
|
+
getOrCreateEntitySourceFile,
|
|
9
|
+
generateCallExpressionExportForDocument,
|
|
10
|
+
linkDocument,
|
|
11
|
+
mergeDataIntoObjectLiteral,
|
|
12
|
+
writeCustomProperty,
|
|
13
|
+
stringify,
|
|
14
|
+
getSysUpdateName,
|
|
15
|
+
FluentDiagnostic,
|
|
16
|
+
EntityData,
|
|
17
|
+
ObjectData,
|
|
18
|
+
} from '@servicenow/sdk-build-core'
|
|
19
|
+
import { Record } from '@servicenow/sdk-core/runtime/db'
|
|
20
|
+
import { ClientScriptSchema } from '@servicenow/sdk-core/runtime/clientscript'
|
|
21
|
+
import { z } from 'zod'
|
|
22
|
+
import * as ts from 'ts-morph'
|
|
23
|
+
import { processScript } from '../ScriptTemplatePlugin'
|
|
24
|
+
import { Diagnostic } from '@servicenow/sdk-project'
|
|
25
|
+
import { except } from './scriptUtils'
|
|
26
|
+
|
|
27
|
+
const UITypeMapping = {
|
|
28
|
+
desktop: 0,
|
|
29
|
+
mobile_or_service_portal: 1,
|
|
30
|
+
all: 10,
|
|
31
|
+
}
|
|
32
|
+
const ClientScriptXmlSchema = ClientScriptSchema.extend({
|
|
33
|
+
table: z.string(),
|
|
34
|
+
ui_type: z.union([z.literal(0), z.literal(1), z.literal(10)]),
|
|
35
|
+
sys_class_name: z.literal('sys_script_client'),
|
|
36
|
+
})
|
|
37
|
+
const PartialClientScriptXMLSchema = ClientScriptXmlSchema.partial()
|
|
38
|
+
|
|
39
|
+
type ClientScriptSchema = z.infer<typeof ClientScriptSchema>
|
|
40
|
+
type PatialClientScriptConfig = z.infer<typeof PartialClientScriptXMLSchema>
|
|
41
|
+
|
|
42
|
+
export default Plugin({
|
|
43
|
+
name: 'ClientScript',
|
|
44
|
+
ownedTables: {
|
|
45
|
+
sys_script_client: { diagnosticLevel: Diagnostic.Level.Warn },
|
|
46
|
+
},
|
|
47
|
+
extractors: {
|
|
48
|
+
entity: {
|
|
49
|
+
CallExpression: (node, context) => {
|
|
50
|
+
const result = extractCallExpression(ClientScript, 'clientScript', node, context, (script) =>
|
|
51
|
+
context.registerExplicitId('sys_script_client', script.$id as string)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (!result.handled || !(0 in result.data)) {
|
|
55
|
+
return result
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const clientScriptEntityData = result.data[0]
|
|
59
|
+
const args = node.getArguments()[0] as ts.ObjectLiteralExpression
|
|
60
|
+
|
|
61
|
+
if (!args) {
|
|
62
|
+
result.diagnostics.push(new FluentDiagnostic(node, 'Client script arguments are missing'))
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const diagnostics = result.diagnostics
|
|
67
|
+
const data = ClientScriptSchema.parse(clientScriptEntityData.getValue())
|
|
68
|
+
const { script, type, field } = data
|
|
69
|
+
|
|
70
|
+
if (field && !type) {
|
|
71
|
+
diagnostics.push(
|
|
72
|
+
new FluentDiagnostic(
|
|
73
|
+
args.getProperty('field')!,
|
|
74
|
+
`Field values are allowed only when type is set to onChange or onCellEdit`
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (script && !validateClientSideScripts(script, context)) {
|
|
80
|
+
diagnostics.push(
|
|
81
|
+
new FluentDiagnostic(
|
|
82
|
+
args.getProperty('script')!,
|
|
83
|
+
`Client side scripts cannot import or require modules.`
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Removing this diagnostic : Currently on instance if you empty script field and click update, it would show basic template set
|
|
90
|
+
* for the field but the xml is not updated. Only after saving it again the xml gets updated with basic template.
|
|
91
|
+
*/
|
|
92
|
+
// if (type && !script) {
|
|
93
|
+
// diagnostics.push(
|
|
94
|
+
// new FluentDiagnostic(
|
|
95
|
+
// args.getProperty('type')!,
|
|
96
|
+
// `Cannot have empty script value for type: ${type}`
|
|
97
|
+
// )
|
|
98
|
+
// )
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
const record = createClientScriptRecord(data, script)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
handled: true,
|
|
105
|
+
diagnostics,
|
|
106
|
+
data: [
|
|
107
|
+
new EntityData(
|
|
108
|
+
'record',
|
|
109
|
+
clientScriptEntityData.getGuid(),
|
|
110
|
+
ObjectData.fromObjectValue(record, node),
|
|
111
|
+
node
|
|
112
|
+
),
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
generators: {
|
|
120
|
+
record(document, context) {
|
|
121
|
+
if (!ClientScriptXmlSchema.safeParse(document.data!['data']).success) {
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return linkDocument(
|
|
126
|
+
document,
|
|
127
|
+
generateCallExpressionExportForDocument(
|
|
128
|
+
context,
|
|
129
|
+
{
|
|
130
|
+
sourceFile: getOrCreateEntitySourceFile(
|
|
131
|
+
context,
|
|
132
|
+
getSysUpdateName(document, 'sys_script_client')
|
|
133
|
+
),
|
|
134
|
+
moduleSpecifier: '@servicenow/sdk/core',
|
|
135
|
+
},
|
|
136
|
+
ClientScript,
|
|
137
|
+
{
|
|
138
|
+
$id: document.guid,
|
|
139
|
+
}
|
|
140
|
+
).getExpressionIfKindOrThrow(ts.SyntaxKind.CallExpression)
|
|
141
|
+
)
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
transformers: {
|
|
146
|
+
record: {
|
|
147
|
+
CallExpression(document, context) {
|
|
148
|
+
if (getCallExpressionName(document.node) !== ClientScript.name) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (document.action === 'DELETE') {
|
|
153
|
+
removeNode(document.node)
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const modifiedClientScriptProps = ClientScriptXmlSchema.partial().safeParse(
|
|
158
|
+
(document.changedData as any).data
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if (!modifiedClientScriptProps.success) {
|
|
162
|
+
return false
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return transformClientScriptArguments(
|
|
166
|
+
document.guid,
|
|
167
|
+
document.node,
|
|
168
|
+
context,
|
|
169
|
+
except(modifiedClientScriptProps.data, (key) => key === 'sys_class_name')
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
function validateClientSideScripts(script: string, context: Context) {
|
|
177
|
+
const source = context.compiler.createSourceFile('tmp-file.ts', script)
|
|
178
|
+
const importDeclarations = source.getDescendantsOfKind(ts.SyntaxKind.ImportDeclaration)
|
|
179
|
+
const requireCalls = source.getDescendantsOfKind(ts.SyntaxKind.CallExpression).filter((callExpression) => {
|
|
180
|
+
return isRequire(callExpression)
|
|
181
|
+
})
|
|
182
|
+
const isValid = !(importDeclarations.length > 0 || requireCalls.length > 0)
|
|
183
|
+
context.compiler.removeSourceFile(source)
|
|
184
|
+
return isValid
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getUITypeFromId(id: number) {
|
|
188
|
+
const type = Object.keys(UITypeMapping).find((e) => UITypeMapping[e] === id)
|
|
189
|
+
if (!type) {
|
|
190
|
+
throw Error('Invalid UI Type encountered, check XML data before transforming again.')
|
|
191
|
+
}
|
|
192
|
+
return type
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function transformClientScriptArguments(
|
|
196
|
+
documentId: string,
|
|
197
|
+
node: ts.CallExpression,
|
|
198
|
+
context: Context,
|
|
199
|
+
data: PatialClientScriptConfig
|
|
200
|
+
) {
|
|
201
|
+
const argNode = node.getArguments()[0]!.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
|
|
202
|
+
const { script, table, ui_type, type, ...rest } = data
|
|
203
|
+
|
|
204
|
+
if (type !== undefined) {
|
|
205
|
+
if (type) {
|
|
206
|
+
writeCustomProperty(argNode, 'type', '', stringify(type))
|
|
207
|
+
} else {
|
|
208
|
+
argNode.getProperty('type')?.remove()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (Object.values(UITypeMapping).some((value) => value === ui_type)) {
|
|
213
|
+
const uiType = getUITypeFromId(ui_type!)
|
|
214
|
+
writeCustomProperty(argNode, 'ui_type', '', stringify(uiType))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (table) {
|
|
218
|
+
writeCustomProperty(argNode, 'table', '{}', stringify(table))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (script !== undefined) {
|
|
222
|
+
processScript(argNode, 'script', script, documentId, 'sys_script_client', context)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
mergeDataIntoObjectLiteral(argNode, rest)
|
|
226
|
+
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function createClientScriptRecord(data: ClientScriptSchema, script: string) {
|
|
231
|
+
data.script = script as any
|
|
232
|
+
const { $id, ...csData } = ClientScriptSchema.parse(data)
|
|
233
|
+
csData.ui_type = getUITypeId(csData.ui_type)
|
|
234
|
+
return Record({
|
|
235
|
+
table: 'sys_script_client',
|
|
236
|
+
$id,
|
|
237
|
+
data: csData as any,
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getUITypeId(value: string) {
|
|
242
|
+
if (value in UITypeMapping) {
|
|
243
|
+
return UITypeMapping[value]
|
|
244
|
+
}
|
|
245
|
+
throw Error('Invalid ui_type found in xml')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function isRequire(callExpression: ts.CallExpression): boolean {
|
|
249
|
+
const expression = callExpression.getExpression()
|
|
250
|
+
return ts.Node.isIdentifier(expression) && expression.getText() === 'require'
|
|
251
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CURRENT_CALL_EXPRESSION, Context, ObjectData } from '@servicenow/sdk-build-core'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { SyntaxKind, Node } from 'ts-morph'
|
|
4
|
+
|
|
5
|
+
export type ScriptInfo = {
|
|
6
|
+
filePath: string
|
|
7
|
+
functionName: string
|
|
8
|
+
isDefault: boolean
|
|
9
|
+
event?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function scriptInfo(node: Node, context: Context, pluginName: string) {
|
|
13
|
+
if (!context.store) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (context.store[CURRENT_CALL_EXPRESSION] !== pluginName) {
|
|
18
|
+
if (pluginName !== 'Table' && (context.store[CURRENT_CALL_EXPRESSION] as string).includes('Column')) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (node.isKind(SyntaxKind.FunctionDeclaration)) {
|
|
24
|
+
return ObjectData.fromObjectValue(
|
|
25
|
+
{
|
|
26
|
+
filePath: path.normalize(node.getSourceFile().getFilePath()),
|
|
27
|
+
functionName: node.getName(),
|
|
28
|
+
isDefault: node.isDefaultExport(),
|
|
29
|
+
},
|
|
30
|
+
node
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (node.isKind(SyntaxKind.FunctionExpression)) {
|
|
35
|
+
const parent = node.getParentIfKind(SyntaxKind.VariableDeclaration)
|
|
36
|
+
if (!parent) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ObjectData.fromObjectValue(
|
|
41
|
+
{
|
|
42
|
+
filePath: path.normalize(node.getSourceFile().getFilePath()),
|
|
43
|
+
functionName: parent.getName(),
|
|
44
|
+
isDefault: parent.isDefaultExport(),
|
|
45
|
+
},
|
|
46
|
+
node
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function except(obj: object, excludePredicate: (string) => boolean) {
|
|
54
|
+
return Object.keys(obj)
|
|
55
|
+
.filter((k) => !excludePredicate(k))
|
|
56
|
+
.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function buildScriptImport(
|
|
60
|
+
scriptFileImport: string,
|
|
61
|
+
importName: string,
|
|
62
|
+
isDefault: boolean,
|
|
63
|
+
context: Context,
|
|
64
|
+
codeFunc: (funcName: string) => string
|
|
65
|
+
) {
|
|
66
|
+
scriptFileImport = scriptFileImport.replace(/\.ts$/, '.js')
|
|
67
|
+
const func_name = importName ?? path.basename(scriptFileImport, '.js')
|
|
68
|
+
let relativeImport = scriptFileImport.replace(context.app.rootDir, '')
|
|
69
|
+
relativeImport = relativeImport.replace(context.app.config.sourceDir, context.app.config.transpiledSourceDir)
|
|
70
|
+
relativeImport = relativeImport.replaceAll(path.sep, path.posix.sep)
|
|
71
|
+
if (relativeImport.startsWith(path.posix.sep)) {
|
|
72
|
+
relativeImport = relativeImport.substring(1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isDefault) {
|
|
76
|
+
scriptFileImport = `const ${func_name} = require('./${relativeImport}').default\n` + codeFunc(func_name)
|
|
77
|
+
} else {
|
|
78
|
+
scriptFileImport = `const { ${func_name} } = require('./${relativeImport}')\n` + codeFunc(func_name)
|
|
79
|
+
}
|
|
80
|
+
return scriptFileImport
|
|
81
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Experience, ExperienceSchema } from '@servicenow/sdk-core/runtime/experience'
|
|
2
|
+
import { Record } from '@servicenow/sdk-core/runtime/db'
|
|
3
|
+
import { EntityData, ObjectData, Plugin, extractCallExpression } from '@servicenow/sdk-build-core'
|
|
4
|
+
import { PARENT_APP_SHELL_ID, WORKSPACE_APP_SHELL_ID } from './constants'
|
|
5
|
+
import { RecordPlugin } from '../db/RecordPlugin'
|
|
6
|
+
import { Diagnostic } from '@servicenow/sdk-project'
|
|
7
|
+
|
|
8
|
+
export default Plugin({
|
|
9
|
+
name: 'Experience',
|
|
10
|
+
ownedTables: {
|
|
11
|
+
sys_ux_app_config: { diagnosticLevel: Diagnostic.Level.Warn },
|
|
12
|
+
},
|
|
13
|
+
extractors: {
|
|
14
|
+
entity: {
|
|
15
|
+
CallExpression: (node, context) =>
|
|
16
|
+
extractCallExpression(Experience, 'experience', node, context, (experience) =>
|
|
17
|
+
context.registerExplicitId('sys_ux_app_config', experience.$id as string)
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
composers: {
|
|
22
|
+
entity: {
|
|
23
|
+
experience(entity, context) {
|
|
24
|
+
const node = entity.getNode()
|
|
25
|
+
const data = entity.getValue()
|
|
26
|
+
const guid = entity.getGuid()
|
|
27
|
+
const recordData = ExperienceSchema.parse(data)
|
|
28
|
+
|
|
29
|
+
const appConfig = Record({
|
|
30
|
+
table: 'sys_ux_app_config',
|
|
31
|
+
$id: guid,
|
|
32
|
+
data: {
|
|
33
|
+
name: recordData.name,
|
|
34
|
+
active: recordData.active,
|
|
35
|
+
base_url_path: recordData.baseUrlPath,
|
|
36
|
+
landing_path: recordData.landingPath,
|
|
37
|
+
description: recordData.description,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const pageRegistry = Record({
|
|
42
|
+
table: 'sys_ux_page_registry',
|
|
43
|
+
$id: context.registerExplicitId('sys_ux_page_registry', `${guid}_registry`),
|
|
44
|
+
data: {
|
|
45
|
+
title: recordData.name,
|
|
46
|
+
active: recordData.active,
|
|
47
|
+
admin_panel: appConfig,
|
|
48
|
+
admin_panel_table: 'sys_ux_app_config',
|
|
49
|
+
parent_app: PARENT_APP_SHELL_ID,
|
|
50
|
+
path: recordData.baseUrlPath,
|
|
51
|
+
root_macroponent: WORKSPACE_APP_SHELL_ID,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return context.composeEntities(
|
|
56
|
+
[appConfig, pageRegistry].map(
|
|
57
|
+
(r) => new EntityData('record', r.$id as string, ObjectData.fromObjectValue(r, node), node)
|
|
58
|
+
),
|
|
59
|
+
[RecordPlugin]
|
|
60
|
+
)
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
})
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { BaseUXRouteSchema, ExperienceSchema, Routes } from '@servicenow/sdk-core/runtime/experience'
|
|
2
|
+
import { Record } from '@servicenow/sdk-core/runtime/db'
|
|
3
|
+
import { SdkError } from '@servicenow/sdk-metrics'
|
|
4
|
+
import { EntityData, ObjectData, Plugin, extractCallExpression } from '@servicenow/sdk-build-core'
|
|
5
|
+
import { parser as uxValueParser, cleanUxValue } from './UxfFormulaParser'
|
|
6
|
+
import { idGenerator } from './tectonicIdGenerator'
|
|
7
|
+
import { WORKSPACE_APP_SHELL_ID } from './constants'
|
|
8
|
+
import { RecordPlugin } from '../db/RecordPlugin'
|
|
9
|
+
import { Diagnostic } from '@servicenow/sdk-project'
|
|
10
|
+
|
|
11
|
+
const PAGE_TEMPLATE_SYS_ID = '19be392623033300f4b4c50947bf65ba'
|
|
12
|
+
type Component = Routes['routes'][string]['component']
|
|
13
|
+
|
|
14
|
+
type RouteParts = {
|
|
15
|
+
pathname: string
|
|
16
|
+
parameters: {
|
|
17
|
+
required: string[]
|
|
18
|
+
optional: string[]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getRouteParts(route: string): RouteParts {
|
|
23
|
+
const chunks = route.split('/').filter((chunk) => chunk && chunk.length > 0)
|
|
24
|
+
if (chunks.length === 0) {
|
|
25
|
+
throw new SdkError('Invalid route - routes may not be empty', { type: 'plugin_error' })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const parts: RouteParts = {
|
|
29
|
+
pathname: chunks[0] || '',
|
|
30
|
+
parameters: {
|
|
31
|
+
required: [],
|
|
32
|
+
optional: [],
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
chunks.forEach((part) => {
|
|
37
|
+
if (part.startsWith(':')) {
|
|
38
|
+
if (part.endsWith('?')) {
|
|
39
|
+
parts.parameters.optional.push(part.slice(1, -1))
|
|
40
|
+
} else {
|
|
41
|
+
parts.parameters.required.push(part.slice(1))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return parts
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const HARDCODED_LAYOUT = {
|
|
50
|
+
default: {
|
|
51
|
+
children: null,
|
|
52
|
+
items: [{ element_id: 'x', styles: {} }],
|
|
53
|
+
root: null,
|
|
54
|
+
rules: null,
|
|
55
|
+
styles: { 'flex-direction': 'column' },
|
|
56
|
+
templateId: '5832fd4d53c31010e6bcddeeff7b12db',
|
|
57
|
+
type: 'flex',
|
|
58
|
+
},
|
|
59
|
+
version: '3.0.0',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const createComposition = (component: Component) => {
|
|
63
|
+
const { tagName, properties = {} } = component
|
|
64
|
+
const componentId = idGenerator(tagName)
|
|
65
|
+
|
|
66
|
+
const UxfFormulaParser = new uxValueParser()
|
|
67
|
+
const propertyValues = Object.keys(properties).reduce((newObj, key) => {
|
|
68
|
+
const parseResult = UxfFormulaParser.parse(properties[key])
|
|
69
|
+
if (parseResult.errors.length) {
|
|
70
|
+
throw new SdkError('Failed to parse formula.', { type: 'plugin_error' })
|
|
71
|
+
}
|
|
72
|
+
newObj[key] = cleanUxValue(parseResult.result.uxValue)
|
|
73
|
+
return newObj
|
|
74
|
+
}, {})
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
definition: { id: componentId, type: 'MACROPONENT' },
|
|
79
|
+
elementId: 'x',
|
|
80
|
+
elementLabel: tagName,
|
|
81
|
+
eventMappings: [],
|
|
82
|
+
isHidden: { type: 'JSON_LITERAL', value: null },
|
|
83
|
+
preset: null,
|
|
84
|
+
propertyValues,
|
|
85
|
+
slot: null,
|
|
86
|
+
styles: null,
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default Plugin({
|
|
92
|
+
name: 'Routes',
|
|
93
|
+
ownedTables: {
|
|
94
|
+
sys_ux_app_route: { diagnosticLevel: Diagnostic.Level.Warn },
|
|
95
|
+
},
|
|
96
|
+
extractors: {
|
|
97
|
+
entity: {
|
|
98
|
+
// TODO: This needs to extract separate entities for each route and the experience. Currently it is extracting an
|
|
99
|
+
// anonymous "container" entity which goes against the design of the plugin architecture.
|
|
100
|
+
CallExpression: (node, context) =>
|
|
101
|
+
extractCallExpression(Routes, 'routes', node, context, () => 'NO_GUID_GENERATED'), // TODO: Generate GUID
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
composers: {
|
|
105
|
+
entity: {
|
|
106
|
+
routes(entity, context) {
|
|
107
|
+
const node = entity.getNode()
|
|
108
|
+
const data = entity.getValue()
|
|
109
|
+
const { experience, routes } = data as Routes
|
|
110
|
+
|
|
111
|
+
const { $id: appConfigId } = ExperienceSchema.parse(experience)
|
|
112
|
+
const appConfigSysId = context.registerExplicitId('sys_ux_app_config', appConfigId)
|
|
113
|
+
|
|
114
|
+
const records: Record[] = []
|
|
115
|
+
for (const [path, route] of Object.entries(routes)) {
|
|
116
|
+
const { $id: routeId, name, component, active, description } = BaseUXRouteSchema.parse(route)
|
|
117
|
+
const routeParts = getRouteParts(path)
|
|
118
|
+
|
|
119
|
+
const screenType = Record({
|
|
120
|
+
table: 'sys_ux_screen_type',
|
|
121
|
+
$id: `${routeId}_screen_type`,
|
|
122
|
+
data: {
|
|
123
|
+
name,
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const routeRecord = Record({
|
|
128
|
+
table: 'sys_ux_app_route',
|
|
129
|
+
$id: routeId,
|
|
130
|
+
data: {
|
|
131
|
+
name,
|
|
132
|
+
app_config: appConfigSysId,
|
|
133
|
+
route_type: routeParts.pathname,
|
|
134
|
+
fields: routeParts.parameters.required.join(','),
|
|
135
|
+
optional_parameters: routeParts.parameters.optional.join(','),
|
|
136
|
+
screen_type: screenType,
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const routeParameters = [...routeParts.parameters.required, ...routeParts.parameters.optional]
|
|
141
|
+
const screenMcp = Record({
|
|
142
|
+
table: 'sys_ux_macroponent',
|
|
143
|
+
$id: `${routeId}_page`,
|
|
144
|
+
data: {
|
|
145
|
+
category: 'page',
|
|
146
|
+
composition: JSON.stringify(createComposition(component), null, 2),
|
|
147
|
+
extends: PAGE_TEMPLATE_SYS_ID,
|
|
148
|
+
internal_event_mappings: JSON.stringify({}),
|
|
149
|
+
layout: JSON.stringify(HARDCODED_LAYOUT, null, 2),
|
|
150
|
+
name: `${name} - Page`,
|
|
151
|
+
// Injecting Route parameters as props of the page, so that they can be passed into the
|
|
152
|
+
// component in the composition.
|
|
153
|
+
props: JSON.stringify(
|
|
154
|
+
routeParameters.map((param) => {
|
|
155
|
+
return {
|
|
156
|
+
defaultValue: '',
|
|
157
|
+
description: null,
|
|
158
|
+
disabled: false,
|
|
159
|
+
fieldType: 'string',
|
|
160
|
+
label: param,
|
|
161
|
+
mandatory: false,
|
|
162
|
+
name: param,
|
|
163
|
+
readOnly: true,
|
|
164
|
+
selectable: false,
|
|
165
|
+
typeMetadata: null,
|
|
166
|
+
valueType: 'string',
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
null,
|
|
170
|
+
2
|
|
171
|
+
),
|
|
172
|
+
schema_version: '1.0.0',
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// TODO(patrick): Still a lot of hardcoded stuff in here - needs
|
|
177
|
+
// to be represented in the config, at least optionally.
|
|
178
|
+
const screen = Record({
|
|
179
|
+
table: 'sys_ux_screen',
|
|
180
|
+
$id: `${routeId}_screen`,
|
|
181
|
+
data: {
|
|
182
|
+
active: active!,
|
|
183
|
+
app_config: appConfigSysId,
|
|
184
|
+
description: description!,
|
|
185
|
+
disable_auto_reflow: false,
|
|
186
|
+
event_mappings: JSON.stringify([]),
|
|
187
|
+
macroponent: screenMcp,
|
|
188
|
+
macroponent_config: JSON.stringify({}),
|
|
189
|
+
name: `${name} - Default`,
|
|
190
|
+
order: 0,
|
|
191
|
+
parent_macroponent: WORKSPACE_APP_SHELL_ID,
|
|
192
|
+
required_translations: JSON.stringify([]),
|
|
193
|
+
screen_type: screenType,
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
records.push(screenType, routeRecord, screenMcp, screen)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return context.composeEntities(
|
|
201
|
+
records.map(
|
|
202
|
+
(r) =>
|
|
203
|
+
new EntityData(
|
|
204
|
+
'record',
|
|
205
|
+
context.registerExplicitId(r.table, r.$id as string),
|
|
206
|
+
ObjectData.fromObjectValue(r, node),
|
|
207
|
+
node
|
|
208
|
+
)
|
|
209
|
+
),
|
|
210
|
+
[RecordPlugin]
|
|
211
|
+
)
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Copied from sn-uxf-formula-parser-library - https://code.devsnc.com/dev/uxf-formula-parser
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* https://code.devsnc.com/dev/uxf-formula-parser/blob/ci/master/src/uxf-formula-parser/clean/cleanUxValue.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const cleanUxValue = (uxValue) => {
|
|
8
|
+
switch (uxValue?.type) {
|
|
9
|
+
case 'JSON_LITERAL':
|
|
10
|
+
return { type: uxValue.type, value: uxValue.value }
|
|
11
|
+
|
|
12
|
+
case 'CONTEXT_BINDING':
|
|
13
|
+
case 'DATA_OUTPUT_BINDING':
|
|
14
|
+
case 'STATE_BINDING':
|
|
15
|
+
case 'EVENT_PAYLOAD_BINDING':
|
|
16
|
+
case 'ELEMENT_BINDING':
|
|
17
|
+
case 'REPEATER_ITEM_BINDING':
|
|
18
|
+
case 'ENV_BINDING':
|
|
19
|
+
return { type: uxValue.type, binding: uxValue.binding }
|
|
20
|
+
|
|
21
|
+
case 'CLIENT_TRANSFORM':
|
|
22
|
+
return {
|
|
23
|
+
type: uxValue.type,
|
|
24
|
+
transform: {
|
|
25
|
+
operator: uxValue.transform.operator,
|
|
26
|
+
operands: cleanUxValue(uxValue.transform.operands),
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
case 'UNARY':
|
|
31
|
+
return {
|
|
32
|
+
type: uxValue.type,
|
|
33
|
+
operation: {
|
|
34
|
+
...uxValue.operation,
|
|
35
|
+
operand: cleanUxValue(uxValue.operation.operand),
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case 'BINARY':
|
|
40
|
+
return {
|
|
41
|
+
type: uxValue.type,
|
|
42
|
+
operation: {
|
|
43
|
+
...uxValue.operation,
|
|
44
|
+
operator: uxValue.operation.operator,
|
|
45
|
+
left: cleanUxValue(uxValue.operation.left),
|
|
46
|
+
right: cleanUxValue(uxValue.operation.right),
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
case 'LIST_CONTAINER':
|
|
51
|
+
return {
|
|
52
|
+
type: uxValue.type,
|
|
53
|
+
container: uxValue.container.map((item) => cleanUxValue(item)),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 'MAP_CONTAINER':
|
|
57
|
+
return {
|
|
58
|
+
type: uxValue.type,
|
|
59
|
+
container: Object.entries(uxValue.container).reduce(
|
|
60
|
+
(acc, [key, item]) => ({
|
|
61
|
+
...acc,
|
|
62
|
+
[key]: cleanUxValue(item),
|
|
63
|
+
}),
|
|
64
|
+
{}
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'TRANSLATION_LITERAL':
|
|
69
|
+
return { type: uxValue.type, value: uxValue.value }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return uxValue
|
|
73
|
+
}
|