@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.
Files changed (166) hide show
  1. package/dist/AttachmentPlugin.d.ts +253 -0
  2. package/dist/AttachmentPlugin.js +216 -0
  3. package/dist/AttachmentPlugin.js.map +1 -0
  4. package/dist/BusinessRulePlugin.d.ts +56 -0
  5. package/dist/BusinessRulePlugin.js +171 -0
  6. package/dist/BusinessRulePlugin.js.map +1 -0
  7. package/dist/CrossScopePrivilegePlugin.d.ts +22 -0
  8. package/dist/CrossScopePrivilegePlugin.js +42 -0
  9. package/dist/CrossScopePrivilegePlugin.js.map +1 -0
  10. package/dist/DefaultPlugin.d.ts +71 -0
  11. package/dist/DefaultPlugin.js +238 -0
  12. package/dist/DefaultPlugin.js.map +1 -0
  13. package/dist/IdPlugin.d.ts +17 -0
  14. package/dist/IdPlugin.js +45 -0
  15. package/dist/IdPlugin.js.map +1 -0
  16. package/dist/ListPlugin.d.ts +91 -0
  17. package/dist/ListPlugin.js +398 -0
  18. package/dist/ListPlugin.js.map +1 -0
  19. package/dist/PropertyPlugin.d.ts +122 -0
  20. package/dist/PropertyPlugin.js +165 -0
  21. package/dist/PropertyPlugin.js.map +1 -0
  22. package/dist/ScriptTemplatePlugin.d.ts +31 -0
  23. package/dist/ScriptTemplatePlugin.js +208 -0
  24. package/dist/ScriptTemplatePlugin.js.map +1 -0
  25. package/dist/UserPreferencePlugin.d.ts +16 -0
  26. package/dist/UserPreferencePlugin.js +30 -0
  27. package/dist/UserPreferencePlugin.js.map +1 -0
  28. package/dist/aclAndRole/AclPlugin.d.ts +117 -0
  29. package/dist/aclAndRole/AclPlugin.js +285 -0
  30. package/dist/aclAndRole/AclPlugin.js.map +1 -0
  31. package/dist/aclAndRole/RolePlugin.d.ts +58 -0
  32. package/dist/aclAndRole/RolePlugin.js +152 -0
  33. package/dist/aclAndRole/RolePlugin.js.map +1 -0
  34. package/dist/aclAndRole/Util.d.ts +3 -0
  35. package/dist/aclAndRole/Util.js +106 -0
  36. package/dist/aclAndRole/Util.js.map +1 -0
  37. package/dist/app/ApplicationMenuPlugin.d.ts +32 -0
  38. package/dist/app/ApplicationMenuPlugin.js +106 -0
  39. package/dist/app/ApplicationMenuPlugin.js.map +1 -0
  40. package/dist/atf/ATFComposer.d.ts +492 -0
  41. package/dist/atf/ATFComposer.js +2717 -0
  42. package/dist/atf/ATFComposer.js.map +1 -0
  43. package/dist/atf/TestPlugin.d.ts +31 -0
  44. package/dist/atf/TestPlugin.js +95 -0
  45. package/dist/atf/TestPlugin.js.map +1 -0
  46. package/dist/atf/index.d.ts +1 -0
  47. package/dist/atf/index.js +9 -0
  48. package/dist/atf/index.js.map +1 -0
  49. package/dist/db/ColumnPlugins.d.ts +278 -0
  50. package/dist/db/ColumnPlugins.js +112 -0
  51. package/dist/db/ColumnPlugins.js.map +1 -0
  52. package/dist/db/RecordPlugin.d.ts +208 -0
  53. package/dist/db/RecordPlugin.js +287 -0
  54. package/dist/db/RecordPlugin.js.map +1 -0
  55. package/dist/db/TablePlugin.d.ts +742 -0
  56. package/dist/db/TablePlugin.js +1249 -0
  57. package/dist/db/TablePlugin.js.map +1 -0
  58. package/dist/db/index.d.ts +3 -0
  59. package/dist/db/index.js +27 -0
  60. package/dist/db/index.js.map +1 -0
  61. package/dist/index.d.ts +16 -0
  62. package/dist/index.js +51 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/scriptedRESTAPI/RESTDeserializationUtils.d.ts +12 -0
  65. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +371 -0
  66. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +1 -0
  67. package/dist/scriptedRESTAPI/RESTSerializationUtils.d.ts +15 -0
  68. package/dist/scriptedRESTAPI/RESTSerializationUtils.js +177 -0
  69. package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +1 -0
  70. package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +144 -0
  71. package/dist/scriptedRESTAPI/RestApiPlugin.js +318 -0
  72. package/dist/scriptedRESTAPI/RestApiPlugin.js.map +1 -0
  73. package/dist/scriptedRESTAPI/RestSchemaUtils.d.ts +190 -0
  74. package/dist/scriptedRESTAPI/RestSchemaUtils.js +53 -0
  75. package/dist/scriptedRESTAPI/RestSchemaUtils.js.map +1 -0
  76. package/dist/scriptedRESTAPI/RestUtils.d.ts +75 -0
  77. package/dist/scriptedRESTAPI/RestUtils.js +469 -0
  78. package/dist/scriptedRESTAPI/RestUtils.js.map +1 -0
  79. package/dist/scripts/ClientScriptPlugin.d.ts +43 -0
  80. package/dist/scripts/ClientScriptPlugin.js +190 -0
  81. package/dist/scripts/ClientScriptPlugin.js.map +1 -0
  82. package/dist/scripts/scriptUtils.d.ts +15 -0
  83. package/dist/scripts/scriptUtils.js +83 -0
  84. package/dist/scripts/scriptUtils.js.map +1 -0
  85. package/dist/uxf/ExperiencePlugin.d.ts +22 -0
  86. package/dist/uxf/ExperiencePlugin.js +55 -0
  87. package/dist/uxf/ExperiencePlugin.js.map +1 -0
  88. package/dist/uxf/RoutesPlugin.d.ts +22 -0
  89. package/dist/uxf/RoutesPlugin.js +176 -0
  90. package/dist/uxf/RoutesPlugin.js.map +1 -0
  91. package/dist/uxf/UxfFormulaParser/cleanUxValue.d.ts +4 -0
  92. package/dist/uxf/UxfFormulaParser/cleanUxValue.js +65 -0
  93. package/dist/uxf/UxfFormulaParser/cleanUxValue.js.map +1 -0
  94. package/dist/uxf/UxfFormulaParser/grammerParser/api.d.ts +189 -0
  95. package/dist/uxf/UxfFormulaParser/grammerParser/api.js +158 -0
  96. package/dist/uxf/UxfFormulaParser/grammerParser/api.js.map +1 -0
  97. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.d.ts +13 -0
  98. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +604 -0
  99. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js.map +1 -0
  100. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.d.ts +12 -0
  101. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
  102. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js.map +1 -0
  103. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.d.ts +31 -0
  104. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +64 -0
  105. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js.map +1 -0
  106. package/dist/uxf/UxfFormulaParser/index.d.ts +3 -0
  107. package/dist/uxf/UxfFormulaParser/index.js +11 -0
  108. package/dist/uxf/UxfFormulaParser/index.js.map +1 -0
  109. package/dist/uxf/UxfFormulaParser/parser.d.ts +8 -0
  110. package/dist/uxf/UxfFormulaParser/parser.js +87 -0
  111. package/dist/uxf/UxfFormulaParser/parser.js.map +1 -0
  112. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.d.ts +8 -0
  113. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js +17 -0
  114. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js.map +1 -0
  115. package/dist/uxf/constants.d.ts +2 -0
  116. package/dist/uxf/constants.js +8 -0
  117. package/dist/uxf/constants.js.map +1 -0
  118. package/dist/uxf/index.d.ts +2 -0
  119. package/dist/uxf/index.js +11 -0
  120. package/dist/uxf/index.js.map +1 -0
  121. package/dist/uxf/tectonicIdGenerator.d.ts +12 -0
  122. package/dist/uxf/tectonicIdGenerator.js +102 -0
  123. package/dist/uxf/tectonicIdGenerator.js.map +1 -0
  124. package/license +9 -0
  125. package/package.json +42 -0
  126. package/src/AttachmentPlugin.ts +262 -0
  127. package/src/BusinessRulePlugin.ts +251 -0
  128. package/src/CrossScopePrivilegePlugin.ts +54 -0
  129. package/src/DefaultPlugin.ts +272 -0
  130. package/src/IdPlugin.ts +47 -0
  131. package/src/ListPlugin.ts +497 -0
  132. package/src/PropertyPlugin.ts +218 -0
  133. package/src/ScriptTemplatePlugin.ts +223 -0
  134. package/src/UserPreferencePlugin.ts +36 -0
  135. package/src/aclAndRole/AclPlugin.ts +410 -0
  136. package/src/aclAndRole/RolePlugin.ts +225 -0
  137. package/src/aclAndRole/Util.ts +104 -0
  138. package/src/app/ApplicationMenuPlugin.ts +158 -0
  139. package/src/atf/ATFComposer.ts +3356 -0
  140. package/src/atf/TestPlugin.ts +119 -0
  141. package/src/atf/index.ts +1 -0
  142. package/src/db/ColumnPlugins.ts +117 -0
  143. package/src/db/RecordPlugin.ts +391 -0
  144. package/src/db/TablePlugin.ts +1581 -0
  145. package/src/db/index.ts +3 -0
  146. package/src/index.ts +16 -0
  147. package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +410 -0
  148. package/src/scriptedRESTAPI/RESTSerializationUtils.ts +227 -0
  149. package/src/scriptedRESTAPI/RestApiPlugin.ts +438 -0
  150. package/src/scriptedRESTAPI/RestSchemaUtils.ts +72 -0
  151. package/src/scriptedRESTAPI/RestUtils.ts +507 -0
  152. package/src/scripts/ClientScriptPlugin.ts +251 -0
  153. package/src/scripts/scriptUtils.ts +81 -0
  154. package/src/uxf/ExperiencePlugin.ts +64 -0
  155. package/src/uxf/RoutesPlugin.ts +215 -0
  156. package/src/uxf/UxfFormulaParser/cleanUxValue.ts +73 -0
  157. package/src/uxf/UxfFormulaParser/grammerParser/api.js +166 -0
  158. package/src/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +606 -0
  159. package/src/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
  160. package/src/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +65 -0
  161. package/src/uxf/UxfFormulaParser/index.ts +4 -0
  162. package/src/uxf/UxfFormulaParser/parser.ts +64 -0
  163. package/src/uxf/UxfFormulaParser/utils/getErrorMsg.ts +13 -0
  164. package/src/uxf/constants.ts +4 -0
  165. package/src/uxf/index.ts +2 -0
  166. package/src/uxf/tectonicIdGenerator.ts +81 -0
@@ -0,0 +1,218 @@
1
+ import { Property, getRolesString, getRolesArray, roleSchema } from '@servicenow/sdk-core/runtime/app'
2
+ import { Record } from '@servicenow/sdk-core/runtime/db'
3
+ import { PropertyType } from '@servicenow/sdk-core/runtime/tables'
4
+ import {
5
+ EntityData,
6
+ FluentDiagnostic,
7
+ ObjectData,
8
+ Plugin,
9
+ extractCallExpression,
10
+ generateCallExpressionExportForDocument,
11
+ getCallExpressionName,
12
+ getOrCreateEntitySourceFile,
13
+ getSysUpdateName,
14
+ linkDocument,
15
+ removeNode,
16
+ transformFunctionArguments,
17
+ writePropertyAsReference,
18
+ } from '@servicenow/sdk-build-core'
19
+ import { RecordPlugin } from './db/RecordPlugin'
20
+ import { z } from 'zod'
21
+ import { SyntaxKind, Node } from 'ts-morph'
22
+ import { pickBy } from 'lodash'
23
+ import { Diagnostic } from '@servicenow/sdk-project'
24
+
25
+ const PropertySchema = z.object({
26
+ $id: z.string().or(z.number()),
27
+ name: z.string(),
28
+ type: PropertyType.or(z.literal('')).default(''),
29
+ value: z.union([z.string(), z.number(), z.boolean()]).default(''),
30
+ description: z.string().default(''),
31
+ ignoreCache: z.boolean().default(false),
32
+ isPrivate: z.boolean().default(false),
33
+ choices: z.array(z.string()).default([]),
34
+ roles: z
35
+ .record(z.union([z.literal('read'), z.literal('write')]), z.array(z.union([roleSchema, z.string()])).optional())
36
+ .default({ read: [], write: [] }),
37
+ })
38
+
39
+ const PropertyConfigSchema = z.object({
40
+ name: PropertySchema.shape.name,
41
+ type: z.string().default(''),
42
+ value: PropertySchema.shape.value.optional(),
43
+ description: PropertySchema.shape.description,
44
+ is_private: PropertySchema.shape.isPrivate,
45
+ ignore_cache: PropertySchema.shape.ignoreCache,
46
+ read_roles: z.string().optional(),
47
+ write_roles: z.string().optional(),
48
+ })
49
+
50
+ export const PropertyDocumentSchema = z.object({
51
+ table: z.literal('sys_properties'),
52
+ data: PropertyConfigSchema,
53
+ })
54
+
55
+ type PropertyConfig = z.output<typeof PropertyConfigSchema>
56
+
57
+ export const transformProperties = (
58
+ data: PropertyConfig,
59
+ xmlRoles: { read_roles: string; write_roles: string }
60
+ ): Partial<Property> => {
61
+ const { read_roles, write_roles, ...rest } = data
62
+
63
+ let roles
64
+ if (read_roles || write_roles) {
65
+ roles = {
66
+ ...(xmlRoles.read_roles && { read: getRolesArray(xmlRoles.read_roles) }),
67
+ ...(xmlRoles.write_roles && { write: getRolesArray(xmlRoles.write_roles) }),
68
+ }
69
+ }
70
+ const result = {
71
+ name: rest.name,
72
+ type: rest.type,
73
+ value: rest.value,
74
+ description: rest.description,
75
+ ignoreCache: rest.ignore_cache,
76
+ isPrivate: rest.is_private,
77
+ roles: roles,
78
+ }
79
+ return pickBy(result, (v) => v !== undefined)
80
+ }
81
+
82
+ export default Plugin({
83
+ name: 'Property',
84
+ ownedTables: {
85
+ sys_properties: { diagnosticLevel: Diagnostic.Level.Warn },
86
+ },
87
+ extractors: {
88
+ entity: {
89
+ CallExpression: (node, context) => {
90
+ const result = extractCallExpression(Property, 'property', node, context, (prop) =>
91
+ context.registerExplicitId('sys_properties', prop.$id as string)
92
+ )
93
+
94
+ if (!result.handled || !(0 in result.data)) {
95
+ return result
96
+ }
97
+
98
+ const propertyData = result.data[0]
99
+ const diagnostics = result.diagnostics
100
+ const scope = context.app.config.scope
101
+ const nameData = propertyData.getProperty('name')
102
+ const nameNode = nameData.getNode()
103
+
104
+ if (!nameData.getValue().startsWith(`${scope}.`)) {
105
+ diagnostics.push(new FluentDiagnostic(nameNode, `Property name must begin with '${scope}.'`))
106
+ }
107
+
108
+ return {
109
+ handled: true,
110
+ diagnostics,
111
+ data: [propertyData],
112
+ }
113
+ },
114
+ },
115
+ },
116
+ composers: {
117
+ entity: {
118
+ property(entity, context) {
119
+ const scope = context.app.config.scope
120
+ const property = PropertySchema.safeParse(entity.getValue())
121
+ if (!property.success) {
122
+ return undefined
123
+ }
124
+
125
+ const { $id, name, roles, value, isPrivate, ignoreCache, choices, type, ...rest } = property.data
126
+
127
+ const readRoles = roles.read ? getRolesString(roles.read) : ''
128
+ const writeRoles = roles.write ? getRolesString(roles.write) : ''
129
+
130
+ const suffix = name.replace(new RegExp(`^${scope}\\.`), '')
131
+ const propertyRecord = Record({
132
+ table: 'sys_properties',
133
+ $id,
134
+ data: {
135
+ name,
136
+ suffix,
137
+ ...rest,
138
+ ...(type === '' ? {} : { type }),
139
+ value: `${value}`, // TODO: Should be allowed to assign non-strings to string columns
140
+ is_private: isPrivate,
141
+ ignore_cache: ignoreCache,
142
+ choices: choices.join('\n'),
143
+ read_roles: readRoles,
144
+ write_roles: writeRoles,
145
+ },
146
+ })
147
+
148
+ return context.composeEntities(
149
+ [
150
+ new EntityData(
151
+ 'record',
152
+ entity.getGuid(),
153
+ ObjectData.fromObjectValue(propertyRecord, entity.getNode()),
154
+ entity.getNode()
155
+ ),
156
+ ],
157
+ [RecordPlugin]
158
+ )
159
+ },
160
+ },
161
+ },
162
+
163
+ generators: {
164
+ record(document, context) {
165
+ if (!PropertyDocumentSchema.safeParse(document.data).success) {
166
+ return undefined
167
+ }
168
+
169
+ return linkDocument(
170
+ document,
171
+ generateCallExpressionExportForDocument(
172
+ context,
173
+ {
174
+ sourceFile: getOrCreateEntitySourceFile(context, getSysUpdateName(document, 'sys_properties')),
175
+ moduleSpecifier: '@servicenow/sdk/core',
176
+ },
177
+ Property,
178
+ { $id: document.guid }
179
+ ).getExpressionIfKindOrThrow(SyntaxKind.CallExpression)
180
+ )
181
+ },
182
+ },
183
+ transformers: {
184
+ record: {
185
+ CallExpression(document) {
186
+ if (getCallExpressionName(document.node) !== Property.name) {
187
+ return false
188
+ }
189
+
190
+ if (document.action === 'DELETE') {
191
+ removeNode(document.node)
192
+ return true
193
+ }
194
+
195
+ if (!document.changedData) {
196
+ return false
197
+ }
198
+ const { read_roles, write_roles } = (document.xmlData as any).data
199
+ const data = transformProperties(document.changedData!['data'] as PropertyConfig, {
200
+ read_roles,
201
+ write_roles,
202
+ })
203
+
204
+ const [args] = document.node.getArguments()
205
+ if (!Node.isObjectLiteralExpression(args)) {
206
+ return false
207
+ }
208
+ transformFunctionArguments(document.node, Property, data)
209
+
210
+ if (data.roles) {
211
+ writePropertyAsReference(args, 'roles', '{}', data.roles)
212
+ }
213
+
214
+ return true
215
+ },
216
+ },
217
+ },
218
+ })
@@ -0,0 +1,223 @@
1
+ import { Plugin, Context, addDefaultImportIfAbsent, StringData, UndefinedData } from '@servicenow/sdk-build-core'
2
+ import * as ts from 'ts-morph'
3
+ import * as path from 'path'
4
+ import { FileSystem } from '@servicenow/sdk-project'
5
+ import * as os from 'os'
6
+
7
+ export const UNIQUE_SCRIPT_FILES = 'unique_script_files'
8
+
9
+ export default Plugin({
10
+ name: 'scriptTemplate',
11
+ extractors: {
12
+ raw: {
13
+ TaggedTemplateExpression: (node) => {
14
+ if (node.getTag().getText() !== 'script') {
15
+ return { handled: false }
16
+ }
17
+
18
+ const value = getTemplateLiteralValue(node)?.trim()
19
+
20
+ return {
21
+ handled: true,
22
+ diagnostics: [],
23
+ data: [typeof value === 'string' ? new StringData(value, node) : new UndefinedData(node)],
24
+ }
25
+ },
26
+
27
+ ExportAssignment: (node, context) => {
28
+ const expression = node.getExpression()
29
+ if (
30
+ !expression ||
31
+ expression.getKind() !== ts.SyntaxKind.TaggedTemplateExpression ||
32
+ expression.asKind(ts.SyntaxKind.TaggedTemplateExpression)!.getTag().getText() !== 'script'
33
+ ) {
34
+ return { handled: false }
35
+ }
36
+
37
+ validateScriptFile(node, context)
38
+ const value = getTemplateLiteralValue(
39
+ node.getExpression().asKind(ts.SyntaxKind.TaggedTemplateExpression)!
40
+ )
41
+
42
+ return {
43
+ handled: true,
44
+ diagnostics: [],
45
+ data: [typeof value === 'string' ? new StringData(value, node) : new UndefinedData(node)],
46
+ }
47
+ },
48
+ },
49
+ },
50
+ })
51
+
52
+ function validateScriptFile(node: ts.ExportAssignment, context: Context) {
53
+ const filePath = path.normalize(node.getSourceFile().getFilePath())
54
+ const uniqueScriptFiles = (context.store[UNIQUE_SCRIPT_FILES] as Set<string>) ?? new Set<string>()
55
+ if (uniqueScriptFiles.has(filePath)) {
56
+ throw Error(
57
+ `Tagged template script files should be unique per entity but expression in ${filePath.replace(
58
+ context.app.rootDir,
59
+ ''
60
+ )} is referenced in multiple nodes.`
61
+ )
62
+ }
63
+ uniqueScriptFiles.add(filePath)
64
+ context.store[UNIQUE_SCRIPT_FILES] = uniqueScriptFiles
65
+ }
66
+
67
+ export function processScript(
68
+ args: ts.ObjectLiteralExpression,
69
+ argName: string,
70
+ script: string,
71
+ docId: string,
72
+ tableName: string,
73
+ context: Context
74
+ ) {
75
+ if (!args.getProperty(argName)) {
76
+ createNewScriptTemplate(args, argName, script, docId, tableName, context)
77
+ } else {
78
+ const prop = args.getProperty(argName)!.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
79
+ const kind = prop.getInitializer()?.getKind()
80
+ if (kind === ts.SyntaxKind.TaggedTemplateExpression) {
81
+ if (shouldCreateScriptFile(script, context)) {
82
+ args.getProperty(argName)?.remove()
83
+ createNewScriptTemplate(args, argName, script, docId, tableName, context)
84
+ } else {
85
+ const taggedTempExp = prop.getInitializer()?.asKind(ts.SyntaxKind.TaggedTemplateExpression)
86
+ updateTaggedTempExpWithScript(taggedTempExp!, script)
87
+ }
88
+ } else if (kind === ts.SyntaxKind.Identifier && checkIfItsScriptTemplate(prop)) {
89
+ const taggedTempExp = getTaggedTemplate(prop)
90
+ updateTaggedTempExpWithScript(taggedTempExp!, script)
91
+ } else {
92
+ args.getProperty(argName)?.remove()
93
+ createNewScriptTemplate(args, argName, script, docId, tableName, context)
94
+ }
95
+ }
96
+ }
97
+
98
+ function getTemplateLiteralValue(node: ts.TaggedTemplateExpression) {
99
+ return node?.getTemplate().asKind(ts.SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralValue()
100
+ }
101
+
102
+ function shouldCreateScriptFile(script: string, context: Context) {
103
+ return script.replaceAll(/\r\n/g, '\n').split('\n').length > context.app.config.maxInlineScriptLines
104
+ }
105
+
106
+ export function formatScript(script: string, spacing: number = 4) {
107
+ const indent = ' '.repeat(spacing)
108
+ return script
109
+ .trim()
110
+ .replaceAll(/\r\n/g, '\n')
111
+ .split('\n')
112
+ .map((line) => `${indent}${line}`)
113
+ .join(os.EOL)
114
+ .replaceAll('`', '\\`')
115
+ .replaceAll('${', '\\${')
116
+ }
117
+
118
+ function templateScript(script: string) {
119
+ script = formatScript(script)
120
+ return `${os.EOL}export default script\`${os.EOL}${script}${os.EOL}\``
121
+ }
122
+
123
+ function getUniqueScriptModuleName(sourceFile: ts.SourceFile) {
124
+ let uniqueScriptName = 'genScript0'
125
+ let i = 1
126
+ const usedIdentifiers = new Set<string>()
127
+ sourceFile.getDescendantsOfKind(ts.SyntaxKind.Identifier).forEach((node) => {
128
+ usedIdentifiers.add(node.getText().trim())
129
+ })
130
+ while (usedIdentifiers.has(uniqueScriptName)) {
131
+ uniqueScriptName = `genScript${i}`
132
+ i += 1
133
+ }
134
+ return uniqueScriptName
135
+ }
136
+
137
+ function createNewScriptTemplate(
138
+ args: ts.ObjectLiteralExpression,
139
+ argName: string,
140
+ script: string,
141
+ docId: string,
142
+ tableName: string,
143
+ context: Context
144
+ ) {
145
+ if (shouldCreateScriptFile(script, context)) {
146
+ const { filePath } = createScriptFile(script, `${tableName}_${docId}`, context)
147
+ const nodeFileDir = path.dirname(path.normalize(args.getSourceFile().getFilePath()))
148
+ let relativePath = path.relative(nodeFileDir, filePath).split(path.sep).join(path.posix.sep)
149
+ relativePath = relativePath.replace(path.extname(relativePath), '')
150
+ relativePath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`
151
+ const uniqueScriptName = getUniqueScriptModuleName(args.getSourceFile())
152
+ args.addPropertyAssignment({ name: argName, initializer: uniqueScriptName })
153
+ addDefaultImportIfAbsent(args.getSourceFile(), relativePath, uniqueScriptName)
154
+ } else {
155
+ const prop = args.addPropertyAssignment({ name: argName, initializer: '"place_holder"' })
156
+ const startPos = prop.getInitializer()!.getStart()
157
+ const lAndCol = args.getSourceFile().getLineAndColumnAtPos(startPos)
158
+ script = formatScript(script, lAndCol.column)
159
+ prop.setInitializer((writer) => {
160
+ writer.writeLine('script`')
161
+ writer.write(script)
162
+ writer.write('`')
163
+ })
164
+ }
165
+ }
166
+
167
+ function updateTaggedTempExpWithScript(node: ts.TaggedTemplateExpression, script: string) {
168
+ const literal = node!.getTemplate().asKind(ts.SyntaxKind.NoSubstitutionTemplateLiteral)!
169
+ const lAndCol = literal.getSourceFile().getLineAndColumnAtPos(node.getStart())
170
+ literal.setLiteralValue(`${os.EOL}${formatScript(script, lAndCol.column)}`)
171
+ }
172
+
173
+ function checkIfItsScriptTemplate(node: ts.PropertyAssignment) {
174
+ const taggedTemp = getTaggedTemplate(node)
175
+ if (!taggedTemp) {
176
+ return false
177
+ }
178
+
179
+ const identifer = taggedTemp.getTag()
180
+ if (!identifer || identifer.getText() !== 'script') {
181
+ return false
182
+ }
183
+
184
+ return true
185
+ }
186
+
187
+ function getTaggedTemplate(node: ts.PropertyAssignment) {
188
+ const definitions = node.getInitializer()?.asKind(ts.SyntaxKind.Identifier)?.getDefinitions()
189
+ if (!definitions || definitions.length > 1) {
190
+ return undefined
191
+ }
192
+
193
+ const exportAssignments = definitions[0]?.getSourceFile().getDescendantsOfKind(ts.SyntaxKind.ExportAssignment)
194
+ if (!exportAssignments || exportAssignments.length > 1) {
195
+ return undefined
196
+ }
197
+
198
+ return exportAssignments[0]?.getExpression()?.asKind(ts.SyntaxKind.TaggedTemplateExpression)
199
+ }
200
+
201
+ const getGeneratedFilePath = (context: Context, fileName: string) => {
202
+ const resolvedDirPath = path.resolve(context.app.rootDir, context.app.config.generatedDir)
203
+ return path.join(resolvedDirPath, fileName)
204
+ }
205
+
206
+ function createScriptFile(script: string, fileName: string, context: Context) {
207
+ let uniqueFileName = `${fileName}_script.now.ts`
208
+ let filePath = getGeneratedFilePath(context, uniqueFileName)
209
+ let i = 0
210
+ while (FileSystem.existsSync(context.fs, filePath)) {
211
+ uniqueFileName = `${fileName}_script${i}.now.ts`
212
+ filePath = getGeneratedFilePath(context, uniqueFileName)
213
+ i++
214
+ }
215
+
216
+ const directory = path.dirname(filePath)
217
+ if (!FileSystem.existsSync(context.fs, directory)) {
218
+ context.fs.mkdirSync(directory, { recursive: true })
219
+ }
220
+
221
+ context.fs.writeFileSync(filePath, templateScript(script), { encoding: 'utf-8' })
222
+ return { filePath }
223
+ }
@@ -0,0 +1,36 @@
1
+ import { UserPreference, UserPreferenceConfig, UserPreferenceSchema } from '@servicenow/sdk-core/runtime/app'
2
+ import { Plugin } from '@servicenow/sdk-build-core'
3
+ import { extractCallExpressionAsRecord } from './db/RecordPlugin'
4
+ import { Diagnostic } from '@servicenow/sdk-project'
5
+ import { Record } from '@servicenow/sdk-core/runtime/db'
6
+
7
+ function asRecord(config: UserPreferenceConfig) {
8
+ const { $id, value, ...rest } = UserPreferenceSchema.parse(config)
9
+ return Record({
10
+ table: 'sys_user_preference',
11
+ $id,
12
+ data: {
13
+ ...rest,
14
+ value: `${value}`, // TODO: Should be allowed to assign non-strings to string columns
15
+ },
16
+ })
17
+ }
18
+
19
+ export default Plugin({
20
+ name: 'UserPreference',
21
+ ownedTables: {
22
+ sys_user_preference: { diagnosticLevel: Diagnostic.Level.Warn },
23
+ },
24
+ extractors: {
25
+ entity: {
26
+ CallExpression: (node, context) =>
27
+ extractCallExpressionAsRecord(
28
+ UserPreference,
29
+ asRecord,
30
+ (pref) => context.registerExplicitId('sys_user_preference', pref.$id as string),
31
+ node,
32
+ context
33
+ ),
34
+ },
35
+ },
36
+ })