@servicenow/sdk-build-core 2.2.9 → 3.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/GUID.js +2 -5
- package/dist/GUID.js.map +1 -1
- package/dist/IncludePaths.d.ts +25 -0
- package/dist/IncludePaths.js +97 -0
- package/dist/IncludePaths.js.map +1 -0
- package/dist/Keys.d.ts +1 -0
- package/dist/Keys.js +3 -0
- package/dist/Keys.js.map +1 -1
- package/dist/XML.d.ts +7 -0
- package/dist/XML.js +19 -2
- package/dist/XML.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/Context.d.ts +12 -5
- package/dist/plugins/behaviors/Composer.d.ts +2 -1
- package/dist/plugins/behaviors/Composer.js.map +1 -1
- package/dist/plugins/behaviors/Serializer.d.ts +3 -2
- package/dist/plugins/behaviors/extractors/Data.d.ts +5 -1
- package/dist/plugins/behaviors/extractors/Data.js +11 -1
- package/dist/plugins/behaviors/extractors/Data.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/util/CallExpression.js +15 -1
- package/dist/plugins/util/CallExpression.js.map +1 -1
- package/dist/plugins/util/CodeTransformation.d.ts +19 -7
- package/dist/plugins/util/CodeTransformation.js +89 -44
- package/dist/plugins/util/CodeTransformation.js.map +1 -1
- package/dist/plugins/util/index.d.ts +0 -1
- package/dist/plugins/util/index.js +0 -1
- package/dist/plugins/util/index.js.map +1 -1
- package/dist/util/Debug.d.ts +1 -5
- package/dist/util/Debug.js +0 -16
- package/dist/util/Debug.js.map +1 -1
- package/dist/util/RuntimeTableSchema.d.ts +5 -0
- package/dist/util/RuntimeTableSchema.js +58 -0
- package/dist/util/RuntimeTableSchema.js.map +1 -0
- package/dist/util/Util.d.ts +0 -4
- package/dist/util/Util.js +0 -14
- package/dist/util/Util.js.map +1 -1
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +1 -1
- package/dist/util/index.js.map +1 -1
- package/package.json +3 -3
- package/src/GUID.ts +1 -1
- package/src/IncludePaths.ts +122 -0
- package/src/Keys.ts +4 -0
- package/src/XML.ts +22 -2
- package/src/index.ts +1 -1
- package/src/plugins/Context.ts +12 -5
- package/src/plugins/behaviors/Composer.ts +2 -1
- package/src/plugins/behaviors/Serializer.ts +3 -2
- package/src/plugins/behaviors/extractors/Data.ts +11 -1
- package/src/plugins/index.ts +1 -1
- package/src/plugins/util/CallExpression.ts +24 -1
- package/src/plugins/util/CodeTransformation.ts +109 -54
- package/src/plugins/util/index.ts +0 -1
- package/src/util/Debug.ts +1 -20
- package/src/util/RuntimeTableSchema.ts +44 -0
- package/src/util/Util.ts +0 -21
- package/src/util/index.ts +1 -1
- package/dist/BuildOptions.d.ts +0 -46
- package/dist/BuildOptions.js +0 -45
- package/dist/BuildOptions.js.map +0 -1
- package/dist/plugins/util/ConfigurationFunction.d.ts +0 -105
- package/dist/plugins/util/ConfigurationFunction.js +0 -377
- package/dist/plugins/util/ConfigurationFunction.js.map +0 -1
- package/dist/util/XMLJsonBuilder.d.ts +0 -18
- package/dist/util/XMLJsonBuilder.js +0 -59
- package/dist/util/XMLJsonBuilder.js.map +0 -1
- package/src/BuildOptions.ts +0 -26
- package/src/plugins/util/ConfigurationFunction.ts +0 -468
- package/src/util/XMLJsonBuilder.ts +0 -64
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { FileSystem, path } from '@servicenow/sdk-project'
|
|
2
|
+
import { Context } from './plugins'
|
|
3
|
+
|
|
4
|
+
export type IncludePaths = {
|
|
5
|
+
[table: string]: {
|
|
6
|
+
fields: { [field: string]: string }[]
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type CustomizedFieldPaths = {
|
|
11
|
+
field: string
|
|
12
|
+
path: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CustomizedIncludePathForTable = {
|
|
16
|
+
table: string
|
|
17
|
+
guid: string
|
|
18
|
+
fields: CustomizedFieldPaths[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CustomizedIncludePaths = {
|
|
22
|
+
[table: string]: CustomizedIncludePathForTable[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DefaultIncludePaths: IncludePaths = {
|
|
26
|
+
// Example of how to define include paths:
|
|
27
|
+
// sys_script_include: {
|
|
28
|
+
// fields: [{ script: '{{name}}.server.js' }],
|
|
29
|
+
// },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const getIncludePaths = (context: Context) => {
|
|
33
|
+
const mappingFileName = path.join(context.app.rootDir, 'now.includes-mapping.json')
|
|
34
|
+
const compilerSourceFile = context.compiler.getSourceFile(mappingFileName)
|
|
35
|
+
let fileText = ''
|
|
36
|
+
if (compilerSourceFile) {
|
|
37
|
+
fileText = compilerSourceFile.getFullText()
|
|
38
|
+
} else {
|
|
39
|
+
// now-sdk fetch doesn't load the file into the compiler, so
|
|
40
|
+
// for now we'll just read the file from the filesystem
|
|
41
|
+
fileText = FileSystem.existsSync(context.fs, mappingFileName)
|
|
42
|
+
? context.fs.readFileSync(mappingFileName).toString()
|
|
43
|
+
: ''
|
|
44
|
+
}
|
|
45
|
+
return fileText ? (JSON.parse(fileText) as IncludePaths) : (DefaultIncludePaths as IncludePaths)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const calculateCustomPathsFromDocs = (context: Context): CustomizedIncludePaths => {
|
|
49
|
+
const includePaths = getIncludePaths(context)
|
|
50
|
+
const customizedIncludePaths = context
|
|
51
|
+
.getAllDocuments()
|
|
52
|
+
.filter((doc) => doc.kind === 'record')
|
|
53
|
+
.map((doc) => ({ table: doc.data?.['table'], guid: doc.guid, data: doc.data?.['data'] }))
|
|
54
|
+
.filter((record) => includePaths[record.table])
|
|
55
|
+
.map((record) => ({
|
|
56
|
+
table: record.table,
|
|
57
|
+
guid: record.guid,
|
|
58
|
+
fields:
|
|
59
|
+
(includePaths[record.table]?.fields || []).flatMap(Object.entries).map(([field, path]) => ({
|
|
60
|
+
field,
|
|
61
|
+
path: replaceTemplate(path, { sys_id: record.guid, ...record.data }),
|
|
62
|
+
})) || [],
|
|
63
|
+
}))
|
|
64
|
+
.reduce((acc, record) => {
|
|
65
|
+
acc[record.table] = acc[record.table] || []
|
|
66
|
+
acc[record.table].push(record)
|
|
67
|
+
return acc
|
|
68
|
+
}, {})
|
|
69
|
+
|
|
70
|
+
return dedupeFilenames(customizedIncludePaths)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function replaceTemplate(template: string, properties: Record<string, any>) {
|
|
74
|
+
let replacedString = template
|
|
75
|
+
const matches = template.matchAll(/{{([a-z_]+)}}/g)
|
|
76
|
+
for (const m of matches) {
|
|
77
|
+
const [, field] = m
|
|
78
|
+
if (field) {
|
|
79
|
+
const sanitizedValue = sanitizeForFilesystem(properties[field])
|
|
80
|
+
replacedString = replacedString.replaceAll(`{{${field}}}`, sanitizedValue)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return replacedString
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const IllegalCharacters = /[/<>?\\:|*"]/g
|
|
87
|
+
const Reserved = /^\.+$/
|
|
88
|
+
const WindowsTrailing = /[. ]+$/
|
|
89
|
+
|
|
90
|
+
function sanitizeForFilesystem(filename: string) {
|
|
91
|
+
return filename.replace(IllegalCharacters, '_').replace(Reserved, '').replace(WindowsTrailing, '')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function dedupeFilename(filePath: string, fixedCount: { [path: string]: number }) {
|
|
95
|
+
const parsedPath = path.parse(filePath)
|
|
96
|
+
fixedCount[filePath] = (fixedCount[filePath] || 0) + 1
|
|
97
|
+
const pathParts = parsedPath.base.split('.')
|
|
98
|
+
pathParts[0] = `${pathParts[0]}-${fixedCount[filePath]}`
|
|
99
|
+
return path.join(parsedPath.dir, pathParts.join('.'))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function dedupeFilenames(customizedFieldPaths: CustomizedIncludePaths) {
|
|
103
|
+
const pathCount = {}
|
|
104
|
+
for (const table in customizedFieldPaths) {
|
|
105
|
+
const paths = customizedFieldPaths[table]!.flatMap((record) => record.fields.map((f) => f.path))
|
|
106
|
+
paths.forEach((p) => (pathCount[p] = (pathCount[p] || 0) + 1))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fixedCount = {}
|
|
110
|
+
for (const table in customizedFieldPaths) {
|
|
111
|
+
const pathForTable = customizedFieldPaths[table] || []
|
|
112
|
+
for (const includePathForTable of pathForTable) {
|
|
113
|
+
for (let i = 0; i < includePathForTable.fields.length; i++) {
|
|
114
|
+
const p = includePathForTable.fields[i]?.path
|
|
115
|
+
if (p && pathCount[p] > 1) {
|
|
116
|
+
includePathForTable.fields[i]!.path = dedupeFilename(p, fixedCount)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return customizedFieldPaths
|
|
122
|
+
}
|
package/src/Keys.ts
CHANGED
|
@@ -141,6 +141,10 @@ export class Keys implements Now.Internal.KeysRegistry {
|
|
|
141
141
|
return !!this.explicit[key]
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
getIdUsingExplicitKey(key: string | number) {
|
|
145
|
+
return this.explicit[key]?.id
|
|
146
|
+
}
|
|
147
|
+
|
|
144
148
|
findExplicitKeyById(id: string) {
|
|
145
149
|
return Object.entries(this.explicit).find(([, v]) => v.id === id)?.[0]
|
|
146
150
|
}
|
package/src/XML.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Action, type Context } from './plugins'
|
|
2
2
|
import { create } from 'xmlbuilder2'
|
|
3
3
|
import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
|
|
4
|
+
import { maybeGetFieldToTagMap } from './util/RuntimeTableSchema'
|
|
4
5
|
|
|
5
6
|
export type XmlValue = string | number | boolean
|
|
6
7
|
|
|
@@ -17,13 +18,22 @@ export function unloadBuilder(context: Context) {
|
|
|
17
18
|
xml,
|
|
18
19
|
end,
|
|
19
20
|
record: (tableName: string, id: string | number, action: Action = 'INSERT_OR_UPDATE') => {
|
|
20
|
-
const rec = recordXml(xml, tableName, id, { attr: { action } })
|
|
21
|
+
const rec = recordXml(xml, tableName, id, { attr: { action }, context })
|
|
21
22
|
rec.addSysScope(context)
|
|
22
23
|
return rec
|
|
23
24
|
},
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* If the code contains the character sequence ']]>' it will be parsed as the end of
|
|
30
|
+
* the CDATA section. To avoid this, we need to split the sequence by dividing the
|
|
31
|
+
* content into two CDATA sections at that point.
|
|
32
|
+
*/
|
|
33
|
+
export function sanitizeNestedCdataTags(content: string): string {
|
|
34
|
+
return content.replace(/]]>/g, ']]]]><![CDATA[>')
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
export function recordXml(
|
|
28
38
|
xml: XMLBuilder,
|
|
29
39
|
tableName: string,
|
|
@@ -31,10 +41,12 @@ export function recordXml(
|
|
|
31
41
|
options: {
|
|
32
42
|
attr?: Record<string, string>
|
|
33
43
|
excludeScopeElement?: boolean
|
|
44
|
+
context?: Context
|
|
34
45
|
} = {}
|
|
35
46
|
) {
|
|
36
47
|
const recordXml = xml.ele(tableName, options.attr || { action: 'INSERT_OR_UPDATE' })
|
|
37
48
|
recordXml.ele('sys_id', undefined).txt(`${id}`)
|
|
49
|
+
const fieldToTagMap = maybeGetFieldToTagMap(tableName, options.context?.getAllDocuments() || [])
|
|
38
50
|
|
|
39
51
|
// Record Builder
|
|
40
52
|
return {
|
|
@@ -49,7 +61,15 @@ export function recordXml(
|
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
let fieldXml
|
|
52
|
-
|
|
64
|
+
const tag = fieldToTagMap[columnName]
|
|
65
|
+
if (
|
|
66
|
+
columnName === 'script' ||
|
|
67
|
+
columnName === 'operation_script' ||
|
|
68
|
+
tag === 'script' ||
|
|
69
|
+
tag === 'html' ||
|
|
70
|
+
tag === 'xml'
|
|
71
|
+
) {
|
|
72
|
+
value = tag === 'xml' ? sanitizeNestedCdataTags(value as string) : value
|
|
53
73
|
fieldXml = recordXml.ele(columnName, attributes).dat(value as string)
|
|
54
74
|
} else {
|
|
55
75
|
fieldXml = recordXml.ele(columnName, attributes).txt(`${value}`)
|
package/src/index.ts
CHANGED
package/src/plugins/Context.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { Keys } from '../Keys'
|
|
14
14
|
import { Plugin } from './Plugin'
|
|
15
15
|
import { FluentDiagnostic } from './Diagnostic'
|
|
16
|
-
import { ProjectContext, ts, type Diagnostic } from '@servicenow/sdk-project'
|
|
16
|
+
import { ProjectContext, SupportedNode, ts, type Diagnostic } from '@servicenow/sdk-project'
|
|
17
17
|
|
|
18
18
|
// These are only imported so they can be referenced in JS docs
|
|
19
19
|
import type {
|
|
@@ -99,7 +99,7 @@ export type Context = ProjectContext & {
|
|
|
99
99
|
* @param plugins An optional array of {@linkcode Plugin}s to use.
|
|
100
100
|
* @returns a {@linkcode LinkedDocument} array.
|
|
101
101
|
*/
|
|
102
|
-
composeEntities(this: Context, data: EntityData[], plugins?: Plugin[]): LinkedDocument[]
|
|
102
|
+
composeEntities(this: Context, data: EntityData[], plugins?: Plugin[]): Promise<LinkedDocument[]>
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Accepts an array of XML data and returns an {@linkcode UnlinkedDocument}
|
|
@@ -204,10 +204,11 @@ export type Context = ProjectContext & {
|
|
|
204
204
|
usageCount: { [key: string]: number }
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
|
-
*
|
|
207
|
+
* Returns an object of XML file names that was marked as handled or ignored by code transformation during the build process.
|
|
208
208
|
*/
|
|
209
|
-
|
|
209
|
+
getHandledXmls: () => Record<string, HandledInfo>
|
|
210
210
|
|
|
211
|
+
updateHandledXMLs: (xml: string | undefined, status: HandledStates, info?: string, node?: SupportedNode) => void
|
|
211
212
|
setAllDocuments: (documents: Document[]) => void
|
|
212
213
|
getAllDocuments: () => Document[]
|
|
213
214
|
getDocumentMap: () => DocumentMap
|
|
@@ -223,10 +224,16 @@ export type Context = ProjectContext & {
|
|
|
223
224
|
keys: Keys
|
|
224
225
|
|
|
225
226
|
explicitKeyExists: Keys['explicitKeyExists']
|
|
227
|
+
getIdUsingExplicitKey: Keys['getIdUsingExplicitKey']
|
|
226
228
|
registerExplicitId: Keys['registerExplicitId']
|
|
227
229
|
registerCompositeId: Keys['registerCompositeId']
|
|
228
230
|
getDeletedAndUnusedIds: Keys['getDeletedAndUnusedIds']
|
|
229
231
|
getKeysSourceFile(): ts.SourceFile
|
|
230
232
|
}
|
|
231
233
|
|
|
232
|
-
export type
|
|
234
|
+
export type HandledInfo = {
|
|
235
|
+
state: HandledStates
|
|
236
|
+
info?: string | undefined
|
|
237
|
+
node?: SupportedNode | undefined
|
|
238
|
+
}
|
|
239
|
+
export type HandledStates = 'handled' | 'handled as record' | 'ignored' | 'skipped' | 'unchanged'
|
|
@@ -37,6 +37,7 @@ export type Document<DocumentKind extends string = string, Node extends Supporte
|
|
|
37
37
|
kind: DocumentKind
|
|
38
38
|
data: unknown
|
|
39
39
|
changedData?: unknown
|
|
40
|
+
xmlFilePath?: string
|
|
40
41
|
xmlData?: unknown
|
|
41
42
|
node?: Node
|
|
42
43
|
action?: Action
|
|
@@ -80,7 +81,7 @@ export type DocumentMap<D extends Document = Document> = {
|
|
|
80
81
|
export type EntityComposerFunction<Data extends Record<string, unknown> = Record<string, unknown>> = (
|
|
81
82
|
entityData: EntityData<Data>,
|
|
82
83
|
context: Context
|
|
83
|
-
) => LinkedDocument | LinkedDocument[] | undefined
|
|
84
|
+
) => Promise<LinkedDocument | LinkedDocument[] | undefined>
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
87
|
* An XML composer function is a function that accepts a piece
|
|
@@ -5,13 +5,14 @@ import { Context } from '../Context'
|
|
|
5
5
|
/**
|
|
6
6
|
* A file is a data structure with a name, a target directory, and content
|
|
7
7
|
* as a string. The target directory must be the name of one of the magic
|
|
8
|
-
* directories that exist within a ServiceNow
|
|
8
|
+
* directories that exist within a ServiceNow installable app package.
|
|
9
9
|
*/
|
|
10
10
|
export type File = {
|
|
11
11
|
name: `${string}.xml`
|
|
12
|
-
directory:
|
|
12
|
+
directory: Directories
|
|
13
13
|
content: string
|
|
14
14
|
}
|
|
15
|
+
export type Directories = 'dictionary' | 'unload' | 'unload.demo' | 'update' | 'apply_once' | 'scope' | ''
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* A serializer function is a function which accepts a document of a known
|
|
@@ -296,11 +296,17 @@ export class EntityData<const D extends Record<string, unknown> = Record<string,
|
|
|
296
296
|
private readonly kind: string,
|
|
297
297
|
private readonly guid: string,
|
|
298
298
|
entity: ObjectData<D>,
|
|
299
|
-
node: SupportedNode
|
|
299
|
+
node: SupportedNode,
|
|
300
|
+
private installMethod?: string,
|
|
301
|
+
private explicitIds?: string[]
|
|
300
302
|
) {
|
|
301
303
|
super(entity.getProperties(), node)
|
|
302
304
|
}
|
|
303
305
|
|
|
306
|
+
getInstallMethod() {
|
|
307
|
+
return this.installMethod
|
|
308
|
+
}
|
|
309
|
+
|
|
304
310
|
getKind() {
|
|
305
311
|
return this.kind
|
|
306
312
|
}
|
|
@@ -308,4 +314,8 @@ export class EntityData<const D extends Record<string, unknown> = Record<string,
|
|
|
308
314
|
getGuid() {
|
|
309
315
|
return this.guid
|
|
310
316
|
}
|
|
317
|
+
|
|
318
|
+
getExplicitIds() {
|
|
319
|
+
return this.explicitIds ?? []
|
|
320
|
+
}
|
|
311
321
|
}
|
package/src/plugins/index.ts
CHANGED
|
@@ -82,6 +82,29 @@ export function extractCallExpression<
|
|
|
82
82
|
return {
|
|
83
83
|
handled: true,
|
|
84
84
|
diagnostics,
|
|
85
|
-
data: new EntityData(
|
|
85
|
+
data: new EntityData(
|
|
86
|
+
kind,
|
|
87
|
+
guid(entityValue),
|
|
88
|
+
entity,
|
|
89
|
+
node,
|
|
90
|
+
extractInstallMethod(entity),
|
|
91
|
+
extractExplicitId(entity)
|
|
92
|
+
),
|
|
86
93
|
}
|
|
87
94
|
}
|
|
95
|
+
|
|
96
|
+
function extractInstallMethod<const D extends Record<string, unknown>>(entity: ObjectData<D>) {
|
|
97
|
+
const $meta = entity.getProperty('$meta')
|
|
98
|
+
if (!$meta) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
return $meta.getValue()['installMethod']
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractExplicitId<const D extends Record<string, unknown>>(entity: ObjectData<D>) {
|
|
105
|
+
const $id = entity.getProperty('$id')
|
|
106
|
+
if (!$id) {
|
|
107
|
+
return []
|
|
108
|
+
}
|
|
109
|
+
return [`${$id.getValue()}`]
|
|
110
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOW_FILE_EXTENSION, SupportedNode, path, ts, tsc } from '@servicenow/sdk-project'
|
|
1
|
+
import { Logger, NOW_FILE_EXTENSION, SupportedNode, path, ts, tsc } from '@servicenow/sdk-project'
|
|
2
2
|
const { format, resolve } = path
|
|
3
3
|
import { Context } from '../Context'
|
|
4
4
|
import { getCallExpressionName } from './CallExpression'
|
|
@@ -6,10 +6,13 @@ import { isArray, isEmpty, isObject, isPlainObject } from 'lodash'
|
|
|
6
6
|
import * as z from 'zod'
|
|
7
7
|
import { Action, Document } from '../behaviors'
|
|
8
8
|
import { isGUID } from '../../GUID'
|
|
9
|
-
import { getPropertyAssignment } from './ObjectLiteral'
|
|
10
9
|
import { noThrow } from '../../util'
|
|
10
|
+
import { getPropertyAssignment } from './ObjectLiteral'
|
|
11
11
|
|
|
12
12
|
export function stringify(val: unknown) {
|
|
13
|
+
if (typeof val === 'function') {
|
|
14
|
+
return val.toString()
|
|
15
|
+
}
|
|
13
16
|
return JSON.stringify(val)
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -28,8 +31,8 @@ export function formatSourceFileName(name: string) {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function getSysUpdateName(document: Document & { xml?: string }, table: string) {
|
|
31
|
-
if (document.
|
|
32
|
-
return path.basename(document.
|
|
34
|
+
if (document.xmlFilePath) {
|
|
35
|
+
return path.basename(document.xmlFilePath, path.extname(document.xmlFilePath))
|
|
33
36
|
}
|
|
34
37
|
return `${table}_${document.guid}`
|
|
35
38
|
}
|
|
@@ -184,7 +187,7 @@ export function getOrCreatePropertyAssignment(
|
|
|
184
187
|
obj: ts.ObjectLiteralExpression,
|
|
185
188
|
name: string,
|
|
186
189
|
initializer: string = 'undefined'
|
|
187
|
-
) {
|
|
190
|
+
): ts.PropertyAssignment {
|
|
188
191
|
const stringifiedName = stringify(name)
|
|
189
192
|
const prop =
|
|
190
193
|
obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment) ??
|
|
@@ -208,49 +211,6 @@ export function getOrCreatePropertyAssignment(
|
|
|
208
211
|
return propOrErr
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
export function createOrUpdateScriptProperty(obj: ts.ObjectLiteralExpression, name: string, value: string) {
|
|
212
|
-
if (!value) {
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const property = obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment)
|
|
217
|
-
if (property) {
|
|
218
|
-
property.remove()
|
|
219
|
-
}
|
|
220
|
-
value = value.replaceAll('`', '\\`').replaceAll('${', '\\${').trim()
|
|
221
|
-
|
|
222
|
-
return createOrUpdateStringLiteralProperty(obj, name, value, '`')
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function createOrUpdateStringLiteralProperty(
|
|
226
|
-
obj: ts.ObjectLiteralExpression,
|
|
227
|
-
name: string,
|
|
228
|
-
value: string | undefined,
|
|
229
|
-
valueQuoteStyle: "'" | '"' | '`' = "'"
|
|
230
|
-
) {
|
|
231
|
-
if (value === undefined) {
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let property = obj.getProperty(name)?.asKind(ts.SyntaxKind.PropertyAssignment)
|
|
236
|
-
|
|
237
|
-
if (!property) {
|
|
238
|
-
property = obj.addPropertyAssignment({ name, initializer: `${valueQuoteStyle}${value}${valueQuoteStyle}` })
|
|
239
|
-
} else {
|
|
240
|
-
const valueNode = property.getChildAtIndexIfKind(2, ts.SyntaxKind.StringLiteral)
|
|
241
|
-
if (valueNode) {
|
|
242
|
-
valueNode.setLiteralValue(value || 'undefined')
|
|
243
|
-
} else {
|
|
244
|
-
const valueNode = property.getChildAtIndexIfKind(2, ts.SyntaxKind.NoSubstitutionTemplateLiteral)
|
|
245
|
-
if (valueNode) {
|
|
246
|
-
valueNode.setLiteralValue(value || 'undefined')
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return property
|
|
252
|
-
}
|
|
253
|
-
|
|
254
214
|
export function isEscapedPropertyAssignment(args: ts.ObjectLiteralExpression, name: string) {
|
|
255
215
|
const prop = args.getProperty(name)
|
|
256
216
|
if (!prop) {
|
|
@@ -289,6 +249,7 @@ function isResolvablePropertyAssignment(args: ts.ObjectLiteralExpression, proper
|
|
|
289
249
|
/** utility function to get the $id value in a object literal node */
|
|
290
250
|
export const getNodeId = (node: ts.ObjectLiteralExpression) => {
|
|
291
251
|
const prop = node.getPropertyOrThrow('$id').asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
|
|
252
|
+
|
|
292
253
|
if (prop.getInitializerIfKind(ts.SyntaxKind.ElementAccessExpression)) {
|
|
293
254
|
const expression = prop.getInitializerIfKind(ts.SyntaxKind.ElementAccessExpression)?.getArgumentExpression()
|
|
294
255
|
return (
|
|
@@ -357,7 +318,20 @@ export const recordSchema = z.object({
|
|
|
357
318
|
table: z.string(),
|
|
358
319
|
})
|
|
359
320
|
|
|
360
|
-
export function
|
|
321
|
+
export function addCallExpressionToProperty(obj: ts.ObjectLiteralExpression, field: string, functionCall) {
|
|
322
|
+
const property = getOrCreatePropertyAssignment(obj, field, functionCall)
|
|
323
|
+
property.setInitializer((writer) => {
|
|
324
|
+
writer.write(functionCall)
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function writePropertyAsReference(
|
|
329
|
+
logger: Logger,
|
|
330
|
+
arg: ts.ObjectLiteralExpression,
|
|
331
|
+
name: string,
|
|
332
|
+
def: string,
|
|
333
|
+
value: unknown
|
|
334
|
+
) {
|
|
361
335
|
if (!value || isArray(value)) {
|
|
362
336
|
return
|
|
363
337
|
}
|
|
@@ -381,7 +355,7 @@ export function writePropertyAsReference(arg: ts.ObjectLiteralExpression, name:
|
|
|
381
355
|
if (isArray(value[property])) {
|
|
382
356
|
writeArrayPropertyAsReference(nestedArgs, property, '[]', value[property])
|
|
383
357
|
} else {
|
|
384
|
-
writePropertyAsReference(nestedArgs, property, '{}', value[property])
|
|
358
|
+
writePropertyAsReference(logger, nestedArgs, property, '{}', value[property])
|
|
385
359
|
}
|
|
386
360
|
}
|
|
387
361
|
|
|
@@ -399,7 +373,7 @@ export function writePropertyAsReference(arg: ts.ObjectLiteralExpression, name:
|
|
|
399
373
|
|
|
400
374
|
// This first write is to check whether the value is a resolvable idenfifier
|
|
401
375
|
// We don't want to throw here if the property assignment would be malformed
|
|
402
|
-
noThrow(() => {
|
|
376
|
+
const maybeError = noThrow(() => {
|
|
403
377
|
propertyAssignment.setInitializer((writer) => {
|
|
404
378
|
if (value === 'default') {
|
|
405
379
|
writer.write(stringify(value))
|
|
@@ -408,6 +382,11 @@ export function writePropertyAsReference(arg: ts.ObjectLiteralExpression, name:
|
|
|
408
382
|
}
|
|
409
383
|
})
|
|
410
384
|
})
|
|
385
|
+
if (maybeError instanceof Error) {
|
|
386
|
+
logger.warn(
|
|
387
|
+
`Invalid reference value '${value}' for property '${name}' in file ${arg.getSourceFile().getFilePath()}`
|
|
388
|
+
)
|
|
389
|
+
}
|
|
411
390
|
if (isResolvablePropertyAssignment(arg, name)) {
|
|
412
391
|
return
|
|
413
392
|
}
|
|
@@ -437,7 +416,7 @@ export function writeCustomProperty(arg: ts.ObjectLiteralExpression, name: strin
|
|
|
437
416
|
export function mergeDataIntoObjectLiteral(node: ts.ObjectLiteralExpression, data: object) {
|
|
438
417
|
const properties = node
|
|
439
418
|
.getProperties()
|
|
440
|
-
.reduce((a, v) => ({ ...a, [(v as ts.PropertyAssignment)
|
|
419
|
+
.reduce((a, v) => ({ ...a, [getObjectPropertyAssignmentName(v as ts.PropertyAssignment)]: v }), {})
|
|
441
420
|
for (const [key, value] of Object.entries(data)) {
|
|
442
421
|
const array = isArray(value)
|
|
443
422
|
if (!isEmpty(properties) && (value === '' || (array && value.length === 0))) {
|
|
@@ -455,6 +434,10 @@ export function mergeDataIntoObjectLiteral(node: ts.ObjectLiteralExpression, dat
|
|
|
455
434
|
if (value !== '' && value !== undefined && !(array && value.length === 0)) {
|
|
456
435
|
const propertyAssignment = properties[key]
|
|
457
436
|
if (propertyAssignment) {
|
|
437
|
+
if (isNowIncludeCall(propertyAssignment.getInitializerIfKind(ts.SyntaxKind.CallExpression))) {
|
|
438
|
+
// Don't transform any updates to Now.includes calls
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
458
441
|
setPropertyAssignmentValue(propertyAssignment, value)
|
|
459
442
|
} else {
|
|
460
443
|
const newPropertyAssignment = getOrCreatePropertyAssignment(node, key, array ? '[]' : '{}')
|
|
@@ -526,6 +509,21 @@ export function filterEmpty(data: any) {
|
|
|
526
509
|
}, {})
|
|
527
510
|
}
|
|
528
511
|
|
|
512
|
+
export function escapeStringLiteralValues(str: string) {
|
|
513
|
+
return str.replace(/[`\\]|\${/g, (char) => {
|
|
514
|
+
switch (char) {
|
|
515
|
+
case '`':
|
|
516
|
+
return '\\`'
|
|
517
|
+
case '\\':
|
|
518
|
+
return '\\' + char
|
|
519
|
+
case '${':
|
|
520
|
+
return '\\${'
|
|
521
|
+
default:
|
|
522
|
+
return char
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
|
|
529
527
|
export function setPropertyAssignmentValue(
|
|
530
528
|
property: ts.PropertyAssignment,
|
|
531
529
|
value: string | ts.PropertyAssignmentStructure | ts.ImportSpecifierStructure | object
|
|
@@ -550,9 +548,14 @@ export function setPropertyAssignmentValue(
|
|
|
550
548
|
const taggedInitializer = property.getInitializerIfKind(ts.SyntaxKind.TaggedTemplateExpression)
|
|
551
549
|
if (taggedInitializer) {
|
|
552
550
|
const template = taggedInitializer.getTemplate()
|
|
553
|
-
|
|
551
|
+
|
|
552
|
+
template.setLiteralValue(escapeStringLiteralValues(value))
|
|
554
553
|
} else {
|
|
555
|
-
|
|
554
|
+
const stringified =
|
|
555
|
+
typeof value !== 'string' || value.indexOf('\n') < 0
|
|
556
|
+
? stringify(value)
|
|
557
|
+
: `\`${escapeStringLiteralValues(value)}\``
|
|
558
|
+
property.setInitializer(stringified)
|
|
556
559
|
}
|
|
557
560
|
}
|
|
558
561
|
|
|
@@ -674,3 +677,55 @@ export function removeNode(node: SupportedNode) {
|
|
|
674
677
|
// behavior configurable.
|
|
675
678
|
node.replaceWithText('undefined')
|
|
676
679
|
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Re-extracts CallExpression property names for transform to check for customized properties on Plugin APIs.
|
|
683
|
+
* If CallExpression properties don't match don't match schema expected by fluent this function will tranform updates
|
|
684
|
+
* only to those properties (not new properties)
|
|
685
|
+
* @param fn type of CallExpression to extract
|
|
686
|
+
* @param node Node
|
|
687
|
+
* @param dataForTransform Document Data passed to a transformer function
|
|
688
|
+
* @param cleanedData Document data cleaned/parsed by a Zod step that has filtered out properties not handled by fluent
|
|
689
|
+
*/
|
|
690
|
+
export function transformCustomizedProperties<
|
|
691
|
+
const A extends unknown[],
|
|
692
|
+
const E extends A extends [infer T extends Record<string, unknown>] ? T : never,
|
|
693
|
+
>(fn: (...args: A) => E, node, dataForTransform, cleanedData) {
|
|
694
|
+
if (!dataForTransform || !cleanedData) {
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const [arg] = node.getArguments()
|
|
699
|
+
const properties = arg?.asKind(ts.SyntaxKind.ObjectLiteralExpression)?.getProperties()
|
|
700
|
+
|
|
701
|
+
if (!properties) {
|
|
702
|
+
return
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const customizedProperties = {}
|
|
706
|
+
Object.values(properties).forEach((prop) => {
|
|
707
|
+
const propName = getObjectPropertyAssignmentName(prop as ts.PropertyAssignment)
|
|
708
|
+
if (dataForTransform[propName] !== undefined && cleanedData[propName] === undefined) {
|
|
709
|
+
customizedProperties[propName] = dataForTransform[propName]
|
|
710
|
+
}
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
if (!isEmpty(customizedProperties)) {
|
|
714
|
+
transformFunctionArguments(node, fn, ...([customizedProperties] as PartialElements<A>))
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function isNowIncludeCall(callExpression?: ts.CallExpression) {
|
|
719
|
+
if (!callExpression) {
|
|
720
|
+
return false
|
|
721
|
+
}
|
|
722
|
+
const propertyAccessExp = callExpression.getExpressionIfKind(ts.SyntaxKind.PropertyAccessExpression)
|
|
723
|
+
const nowNamespace = propertyAccessExp?.getExpressionIfKind(ts.SyntaxKind.Identifier)?.getText()
|
|
724
|
+
const functionName = propertyAccessExp?.getName()
|
|
725
|
+
return nowNamespace === 'Now' && functionName === 'include'
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export function getObjectPropertyAssignmentName(objectProperty: ts.PropertyAssignment) {
|
|
729
|
+
const nameNode = objectProperty.getNameNode()
|
|
730
|
+
return nameNode.isKind(ts.SyntaxKind.StringLiteral) ? nameNode.getLiteralValue() : objectProperty.getName()
|
|
731
|
+
}
|
package/src/util/Debug.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inspect as nodeInspect } from 'util'
|
|
2
|
-
import {
|
|
2
|
+
import { Document } from '../plugins/behaviors'
|
|
3
3
|
import { Logger } from '@servicenow/sdk-project'
|
|
4
4
|
|
|
5
5
|
export function inspect(val: unknown, depth = 2) {
|
|
@@ -10,21 +10,6 @@ export function inspect(val: unknown, depth = 2) {
|
|
|
10
10
|
})
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function debugData(data: ({ data: unknown } | Data)[], logger: Logger, message = 'DATA:') {
|
|
14
|
-
logger.debug(
|
|
15
|
-
`${message} ${inspect(
|
|
16
|
-
data.map((d) => {
|
|
17
|
-
if (d instanceof Data) {
|
|
18
|
-
return d.getValue()
|
|
19
|
-
} else {
|
|
20
|
-
return d
|
|
21
|
-
}
|
|
22
|
-
}),
|
|
23
|
-
5
|
|
24
|
-
)}`
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
13
|
export function debugDocuments(documents: Document[], logger: Logger, message = 'DOCUMENTS:') {
|
|
29
14
|
logger.debug(
|
|
30
15
|
`${message} ${inspect(
|
|
@@ -37,7 +22,3 @@ export function debugDocuments(documents: Document[], logger: Logger, message =
|
|
|
37
22
|
)}`
|
|
38
23
|
)
|
|
39
24
|
}
|
|
40
|
-
|
|
41
|
-
export function debugFiles(files: File[], logger: Logger, message = 'FILES:') {
|
|
42
|
-
logger.debug(`${message}: ${inspect(files, 5)}`)
|
|
43
|
-
}
|