@servicenow/sdk-build-plugins 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acl-plugin.js +13 -4
- package/dist/acl-plugin.js.map +1 -1
- package/dist/application-menu-plugin.js +1 -0
- package/dist/application-menu-plugin.js.map +1 -1
- package/dist/atf/step-configs.d.ts +13 -12
- package/dist/atf/step-configs.js.map +1 -1
- package/dist/atf/test-plugin.d.ts +1 -1
- package/dist/atf/test-plugin.js +8 -5
- package/dist/atf/test-plugin.js.map +1 -1
- package/dist/basic-syntax-plugin.js +51 -13
- package/dist/basic-syntax-plugin.js.map +1 -1
- package/dist/business-rule-plugin.js.map +1 -1
- package/dist/claims-plugin.js +1 -1
- package/dist/claims-plugin.js.map +1 -1
- package/dist/client-script-plugin.js +5 -17
- package/dist/client-script-plugin.js.map +1 -1
- package/dist/column/column-helper.d.ts +1 -1
- package/dist/column/column-helper.js +46 -2
- package/dist/column/column-helper.js.map +1 -1
- package/dist/column/column-to-record.js +6 -4
- package/dist/column/column-to-record.js.map +1 -1
- package/dist/column-plugin.js +106 -27
- package/dist/column-plugin.js.map +1 -1
- package/dist/data-plugin.d.ts +3 -0
- package/dist/data-plugin.js +208 -0
- package/dist/data-plugin.js.map +1 -0
- package/dist/import-sets-plugin.d.ts +2 -0
- package/dist/import-sets-plugin.js +412 -0
- package/dist/import-sets-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/json-plugin.d.ts +4 -4
- package/dist/json-plugin.js +21 -7
- package/dist/json-plugin.js.map +1 -1
- package/dist/list-plugin.js +83 -1
- package/dist/list-plugin.js.map +1 -1
- package/dist/now-attach-plugin.d.ts +35 -0
- package/dist/now-attach-plugin.js +317 -0
- package/dist/now-attach-plugin.js.map +1 -0
- package/dist/now-config-plugin.js +3 -0
- package/dist/now-config-plugin.js.map +1 -1
- package/dist/now-include-plugin.js +7 -1
- package/dist/now-include-plugin.js.map +1 -1
- package/dist/package-json-plugin.js +2 -2
- package/dist/package-json-plugin.js.map +1 -1
- package/dist/record-plugin.d.ts +6 -0
- package/dist/record-plugin.js +50 -23
- package/dist/record-plugin.js.map +1 -1
- package/dist/repack/lint/Rules.js.map +1 -1
- package/dist/rest-api-plugin.js +28 -31
- package/dist/rest-api-plugin.js.map +1 -1
- package/dist/role-plugin.js +1 -0
- package/dist/role-plugin.js.map +1 -1
- package/dist/server-module-plugin/index.js +15 -2
- package/dist/server-module-plugin/index.js.map +1 -1
- package/dist/service-portal/widget-plugin.js +4 -1
- package/dist/service-portal/widget-plugin.js.map +1 -1
- package/dist/static-content-plugin.d.ts +1 -0
- package/dist/static-content-plugin.js +4 -3
- package/dist/static-content-plugin.js.map +1 -1
- package/dist/table-plugin.js +33 -2
- package/dist/table-plugin.js.map +1 -1
- package/dist/ui-page-plugin.js +2 -1
- package/dist/ui-page-plugin.js.map +1 -1
- package/dist/ui-policy-plugin.d.ts +2 -0
- package/dist/ui-policy-plugin.js +407 -0
- package/dist/ui-policy-plugin.js.map +1 -0
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +24 -0
- package/dist/utils.js.map +1 -1
- package/dist/view-plugin.js +1 -1
- package/dist/view-plugin.js.map +1 -1
- package/package.json +11 -7
- package/src/_types/eslint-plugin-es-x.d.ts +17 -0
- package/src/_types/md5.js.d.ts +8 -0
- package/src/acl-plugin.ts +19 -9
- package/src/application-menu-plugin.ts +1 -0
- package/src/atf/step-configs.ts +14 -12
- package/src/atf/test-plugin.ts +40 -21
- package/src/basic-syntax-plugin.ts +61 -13
- package/src/business-rule-plugin.ts +7 -4
- package/src/claims-plugin.ts +1 -1
- package/src/client-script-plugin.ts +8 -22
- package/src/column/column-helper.ts +65 -3
- package/src/column/column-to-record.ts +6 -4
- package/src/column-plugin.ts +141 -39
- package/src/data-plugin.ts +266 -0
- package/src/import-sets-plugin.ts +542 -0
- package/src/index.ts +4 -0
- package/src/json-plugin.ts +31 -12
- package/src/list-plugin.ts +91 -1
- package/src/now-attach-plugin.ts +399 -0
- package/src/now-config-plugin.ts +6 -2
- package/src/now-include-plugin.ts +8 -1
- package/src/package-json-plugin.ts +3 -3
- package/src/record-plugin.ts +61 -30
- package/src/repack/lint/Rules.ts +1 -10
- package/src/rest-api-plugin.ts +45 -51
- package/src/role-plugin.ts +1 -0
- package/src/server-module-plugin/index.ts +21 -5
- package/src/service-portal/widget-plugin.ts +4 -1
- package/src/static-content-plugin.ts +2 -2
- package/src/table-plugin.ts +47 -7
- package/src/ui-page-plugin.ts +2 -1
- package/src/ui-policy-plugin.ts +509 -0
- package/src/utils.ts +27 -1
- package/src/view-plugin.ts +1 -1
package/src/list-plugin.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
StringShape,
|
|
7
7
|
type ObjectShape,
|
|
8
8
|
type Diagnostics,
|
|
9
|
+
Database,
|
|
9
10
|
} from '@servicenow/sdk-build-core'
|
|
10
11
|
import { create } from 'xmlbuilder2'
|
|
11
12
|
import { generateDeprecatedDiagnostics } from './utils'
|
|
@@ -33,6 +34,35 @@ function generateListColumnProps(column: ObjectShape, index: number, diagnostics
|
|
|
33
34
|
return props
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function getListUpdateName(list: Record): string {
|
|
38
|
+
const { view: viewShape, parent: parentShape, relationship: relShape, name: tableShape } = list.properties()
|
|
39
|
+
const viewRecordId = viewShape?.isRecord() ? viewShape.getId() : viewShape?.isRecordId() ? viewShape : undefined
|
|
40
|
+
const parent = parentShape?.isString() ? parentShape.asString().getValue() : undefined
|
|
41
|
+
const relationship = relShape?.ifDefined()
|
|
42
|
+
? (relShape.ifString()?.getValue() ?? relShape.asRecord().getId().getValue())
|
|
43
|
+
: undefined
|
|
44
|
+
const table = tableShape?.isString() ? tableShape.asString().getValue() : undefined
|
|
45
|
+
let view = viewRecordId?.isDefined()
|
|
46
|
+
? ((viewRecordId?.getKey('name') as string) ?? 'NULL')
|
|
47
|
+
: viewShape?.asString().getValue()
|
|
48
|
+
view =
|
|
49
|
+
viewRecordId && (viewRecordId.getKey('name') === DEFAULT_VIEW || viewRecordId.getValue() === DEFAULT_VIEW)
|
|
50
|
+
? 'NULL'
|
|
51
|
+
: view
|
|
52
|
+
|
|
53
|
+
const updateNameFormats = new Map([
|
|
54
|
+
['no-parent-no-relationship', [table, view]],
|
|
55
|
+
['parent-no-relationship', [table, parent, view]],
|
|
56
|
+
['no-parent-relationship', ['NULL', relationship, view]],
|
|
57
|
+
['parent-relationship', [parent, relationship, view]],
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
const key = `${parent ? 'parent' : 'no-parent'}-${relationship ? 'relationship' : 'no-relationship'}`
|
|
61
|
+
const segments = updateNameFormats.get(key) ?? []
|
|
62
|
+
|
|
63
|
+
return ['sys_ui_list', ...segments].join('_').toLocaleLowerCase()
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
export const ListPlugin = Plugin.create({
|
|
37
67
|
name: 'ListPlugin',
|
|
38
68
|
records: {
|
|
@@ -49,6 +79,7 @@ export const ListPlugin = Plugin.create({
|
|
|
49
79
|
inverse: true,
|
|
50
80
|
},
|
|
51
81
|
},
|
|
82
|
+
getUpdateName: (list) => ({ success: true, value: getListUpdateName(list) }),
|
|
52
83
|
toShape(record, { descendants }) {
|
|
53
84
|
return {
|
|
54
85
|
success: true,
|
|
@@ -160,12 +191,71 @@ export const ListPlugin = Plugin.create({
|
|
|
160
191
|
success: true,
|
|
161
192
|
value: {
|
|
162
193
|
source: list,
|
|
163
|
-
name:
|
|
194
|
+
name: `${getListUpdateName(list)}.xml`,
|
|
164
195
|
category: list.getInstallCategory(),
|
|
165
196
|
content: xml.end({ prettyPrint: true }),
|
|
166
197
|
},
|
|
167
198
|
}
|
|
168
199
|
},
|
|
200
|
+
async diff(existing, incoming, { factory }) {
|
|
201
|
+
if (incoming.query().length === 0 || existing.query().length === 0) {
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
value: incoming.query().length === 0 ? new Database() : new Database(incoming.query()),
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const changeDatabase = new Database()
|
|
208
|
+
let hasChanges = false
|
|
209
|
+
const existingList = existing.query('sys_ui_list')[0]
|
|
210
|
+
const incomingList = incoming.query('sys_ui_list')[0]
|
|
211
|
+
const existingElements = existing.query('sys_ui_list_element')
|
|
212
|
+
const incomingElements = incoming.query('sys_ui_list_element')
|
|
213
|
+
|
|
214
|
+
if (incomingList && existingList) {
|
|
215
|
+
if (!existingList.strictEquals(incomingList)) {
|
|
216
|
+
changeDatabase.insert(existingList.merge(incomingList))
|
|
217
|
+
hasChanges = true
|
|
218
|
+
}
|
|
219
|
+
} else if (incomingList) {
|
|
220
|
+
changeDatabase.insert(incomingList)
|
|
221
|
+
hasChanges = true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const markForRemoval: Record[] = []
|
|
225
|
+
for (const element of existingElements) {
|
|
226
|
+
const match = incoming.resolve(element.getId())
|
|
227
|
+
if (!match) {
|
|
228
|
+
hasChanges = true
|
|
229
|
+
markForRemoval.push(element)
|
|
230
|
+
} else {
|
|
231
|
+
hasChanges = hasChanges || !element.strictEquals(match)
|
|
232
|
+
changeDatabase.insert(element.merge(match))
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
incomingElements.forEach((element) => {
|
|
237
|
+
const match = changeDatabase.resolve(element.getId())
|
|
238
|
+
if (!match) {
|
|
239
|
+
changeDatabase.insert(element)
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
for (const element of markForRemoval) {
|
|
244
|
+
const deleteRecord = await factory.createRecord({
|
|
245
|
+
source: element.getSource(),
|
|
246
|
+
table: 'sys_ui_list_element',
|
|
247
|
+
explicitId: element.getId(),
|
|
248
|
+
properties: element.properties(),
|
|
249
|
+
action: 'DELETE',
|
|
250
|
+
})
|
|
251
|
+
changeDatabase.insert(deleteRecord)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
value: hasChanges ? changeDatabase : new Database(),
|
|
257
|
+
}
|
|
258
|
+
},
|
|
169
259
|
},
|
|
170
260
|
sys_ui_list_element: {
|
|
171
261
|
coalesce: ['list_id', 'element'],
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import {
|
|
2
|
+
path as pathModule,
|
|
3
|
+
CallExpressionShape,
|
|
4
|
+
FileSystem,
|
|
5
|
+
Plugin,
|
|
6
|
+
type Source,
|
|
7
|
+
type Record,
|
|
8
|
+
crypto,
|
|
9
|
+
path,
|
|
10
|
+
type Factory,
|
|
11
|
+
type AssociateRecordsShape,
|
|
12
|
+
type LazyValue,
|
|
13
|
+
gunzip,
|
|
14
|
+
ObjectShape,
|
|
15
|
+
ts,
|
|
16
|
+
gzipSync,
|
|
17
|
+
isLazyValue,
|
|
18
|
+
hasAssociatedRecords,
|
|
19
|
+
unloadBuilder,
|
|
20
|
+
} from '@servicenow/sdk-build-core'
|
|
21
|
+
import { CHUNK_SIZE, chunkData, generateId } from './static-content-plugin'
|
|
22
|
+
|
|
23
|
+
export async function sha256(message: Buffer) {
|
|
24
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', new Uint8Array(message))
|
|
25
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
26
|
+
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ImageAttachmentTypes = new Map([
|
|
30
|
+
['.jpg', 'image/jpeg'],
|
|
31
|
+
['.jpeg', 'image/jpeg'],
|
|
32
|
+
['.png', 'image/png'],
|
|
33
|
+
['.bmp', 'image/bmp'],
|
|
34
|
+
['.gif', 'image/gif'],
|
|
35
|
+
['.ico', 'image/ico'],
|
|
36
|
+
['.svg', 'image/svg+xml'],
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
function getExtension(contentType: string) {
|
|
40
|
+
switch (contentType) {
|
|
41
|
+
case 'image/jpeg':
|
|
42
|
+
return '.jpg'
|
|
43
|
+
case 'image/png':
|
|
44
|
+
return '.png'
|
|
45
|
+
case 'image/bmp':
|
|
46
|
+
return '.bmp'
|
|
47
|
+
case 'image/gif':
|
|
48
|
+
return '.gif'
|
|
49
|
+
case 'image/vnd.microsoft.icon':
|
|
50
|
+
case 'image/x-icon':
|
|
51
|
+
case 'image/ico':
|
|
52
|
+
return '.ico'
|
|
53
|
+
case 'image/svg':
|
|
54
|
+
case 'image/svg+xml':
|
|
55
|
+
return '.svg'
|
|
56
|
+
default:
|
|
57
|
+
return ''
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type BaseAttachmentProperties = {
|
|
62
|
+
average_image_color: ''
|
|
63
|
+
chunk_size_bytes: unknown
|
|
64
|
+
compressed: boolean
|
|
65
|
+
content_type: string | undefined
|
|
66
|
+
hash: string
|
|
67
|
+
image_height: ''
|
|
68
|
+
image_width: ''
|
|
69
|
+
size_bytes: unknown
|
|
70
|
+
size_compressed: unknown
|
|
71
|
+
data: string[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class NowAttachShape extends CallExpressionShape implements AssociateRecordsShape, LazyValue {
|
|
75
|
+
private readonly baseProperties: BaseAttachmentProperties
|
|
76
|
+
|
|
77
|
+
constructor({
|
|
78
|
+
source,
|
|
79
|
+
path,
|
|
80
|
+
baseProperties,
|
|
81
|
+
}: { source: Source; path: string; baseProperties: BaseAttachmentProperties }) {
|
|
82
|
+
super({ source, callee: 'Now.attach', args: [path] })
|
|
83
|
+
|
|
84
|
+
this.baseProperties = baseProperties
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
evaluate(parentRecord: Record, field: string) {
|
|
88
|
+
return generateId(parentRecord.getId().getValue(), 'sys_attachment', this.baseProperties.hash, field)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getPath(): string {
|
|
92
|
+
return this.getArgument(0).asString().getValue()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getBaseAttachmentProperties() {
|
|
96
|
+
return this.baseProperties
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getBufferData() {
|
|
100
|
+
const buffers = await Promise.all(this.getBaseAttachmentProperties().data.map((d) => Buffer.from(d, 'base64')))
|
|
101
|
+
const combinedBuffer = Buffer.concat(buffers)
|
|
102
|
+
return await gunzip(combinedBuffer)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async createAssociatedRecords({
|
|
106
|
+
parentRecord,
|
|
107
|
+
factory,
|
|
108
|
+
field,
|
|
109
|
+
}: {
|
|
110
|
+
parentRecord: Record
|
|
111
|
+
factory: Factory
|
|
112
|
+
field: string
|
|
113
|
+
}) {
|
|
114
|
+
const attachment = await factory.createRecord({
|
|
115
|
+
source: parentRecord.getSource(),
|
|
116
|
+
table: 'sys_attachment',
|
|
117
|
+
properties: {
|
|
118
|
+
sys_id: generateId(parentRecord.getId().getValue(), 'sys_attachment', this.baseProperties.hash, field),
|
|
119
|
+
average_image_color: this.baseProperties.average_image_color,
|
|
120
|
+
chunk_size_bytes: this.baseProperties.chunk_size_bytes,
|
|
121
|
+
compressed: this.baseProperties.compressed,
|
|
122
|
+
content_type: this.baseProperties.content_type,
|
|
123
|
+
hash: this.baseProperties.hash,
|
|
124
|
+
image_height: this.baseProperties.image_height,
|
|
125
|
+
image_width: this.baseProperties.image_width,
|
|
126
|
+
size_bytes: this.baseProperties.size_bytes,
|
|
127
|
+
size_compressed: this.baseProperties.size_compressed,
|
|
128
|
+
file_name: field,
|
|
129
|
+
table_name: `ZZ_YY${parentRecord.getTable()}`,
|
|
130
|
+
table_sys_id: parentRecord.getId().getValue(),
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const attachmentDocs = await Promise.all(
|
|
135
|
+
this.baseProperties.data.map((data, i) =>
|
|
136
|
+
factory.createRecord({
|
|
137
|
+
source: parentRecord.getSource(),
|
|
138
|
+
table: 'sys_attachment_doc',
|
|
139
|
+
properties: {
|
|
140
|
+
sys_id: generateId(parentRecord.getId().getValue(), 'sys_attachment_doc', field, data),
|
|
141
|
+
data,
|
|
142
|
+
length: data.length,
|
|
143
|
+
position: i,
|
|
144
|
+
sys_attachment: attachment.getId().getValue(),
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return [attachment, ...attachmentDocs]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static async create(source: Source, fs: FileSystem, filePath: string) {
|
|
154
|
+
const buffer = (await fs.readFileSync(filePath)) as Buffer
|
|
155
|
+
|
|
156
|
+
const hash = await sha256(buffer)
|
|
157
|
+
const gzipped = gzipSync(buffer)
|
|
158
|
+
const compressedFileData = Buffer.from(gzipped)
|
|
159
|
+
|
|
160
|
+
// The first sys_attachment_doc (position=0), contains only the
|
|
161
|
+
// first 10 bytes of the gzip header, which are base64-encoded into 16 characters.
|
|
162
|
+
// For more info on the gzip header: https://www.ietf.org/rfc/rfc1952.txt
|
|
163
|
+
const gzipHeader = compressedFileData.subarray(0, 10).toString('base64')
|
|
164
|
+
const remainingCompressedData = chunkData(compressedFileData.subarray(10).toString('base64'))
|
|
165
|
+
|
|
166
|
+
return new NowAttachShape({
|
|
167
|
+
source,
|
|
168
|
+
path: filePath,
|
|
169
|
+
baseProperties: {
|
|
170
|
+
average_image_color: '',
|
|
171
|
+
chunk_size_bytes: CHUNK_SIZE,
|
|
172
|
+
compressed: true,
|
|
173
|
+
content_type: ImageAttachmentTypes.get(path.extname(filePath).toLowerCase()),
|
|
174
|
+
hash,
|
|
175
|
+
image_height: '',
|
|
176
|
+
image_width: '',
|
|
177
|
+
size_bytes: buffer.length,
|
|
178
|
+
size_compressed: gzipped.length,
|
|
179
|
+
data: [gzipHeader, ...remainingCompressedData],
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static fromAttachmentRecord(parentUpdateName: string, attachment: Record, attachmentDocs: Record[]) {
|
|
185
|
+
const field = attachment.get('file_name').getValue()
|
|
186
|
+
const extension = getExtension(attachment.get('content_type').toString().getValue() || '')
|
|
187
|
+
const relativePath = `.${path.sep}${parentUpdateName}_${field}${extension}`
|
|
188
|
+
|
|
189
|
+
return new NowAttachShape({
|
|
190
|
+
source: attachment,
|
|
191
|
+
path: relativePath,
|
|
192
|
+
baseProperties: {
|
|
193
|
+
average_image_color: '',
|
|
194
|
+
chunk_size_bytes: attachment.get('chunk_size_bytes').getValue(),
|
|
195
|
+
compressed: attachment.get('compressed').toBoolean().getValue(),
|
|
196
|
+
content_type: attachment.get('content_type').toString().getValue() || undefined,
|
|
197
|
+
hash: attachment.get('hash').toString().getValue(),
|
|
198
|
+
image_height: '',
|
|
199
|
+
image_width: '',
|
|
200
|
+
size_bytes: attachment.get('size_bytes').getValue(),
|
|
201
|
+
size_compressed: attachment.get('size_compressed').getValue(),
|
|
202
|
+
data: attachmentDocs.map((doc) => doc.get('data').toString().getValue()),
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const NowAttachPlugin = Plugin.create({
|
|
209
|
+
name: 'NowAttachPlugin',
|
|
210
|
+
records: {
|
|
211
|
+
'*': {
|
|
212
|
+
async toFile(record, { config, database, factory, transform }) {
|
|
213
|
+
const recordBuilder = unloadBuilder(config)
|
|
214
|
+
const updateName = await transform.getUpdateName(record)
|
|
215
|
+
const builder = recordBuilder.record(record, updateName)
|
|
216
|
+
let attachmentsProcessed = false
|
|
217
|
+
|
|
218
|
+
record
|
|
219
|
+
.entries()
|
|
220
|
+
.sort(([a], [b]) => a.localeCompare(b)) // Sort keys to make outputs more deterministic
|
|
221
|
+
.forEach(([prop, shape]) => {
|
|
222
|
+
if (isLazyValue(shape)) {
|
|
223
|
+
attachmentsProcessed = true
|
|
224
|
+
builder.field(prop, shape.evaluate(record, prop))
|
|
225
|
+
} else {
|
|
226
|
+
builder.field(prop, shape)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
for (const field in record.properties()) {
|
|
231
|
+
const shape = record.get(field)
|
|
232
|
+
if (hasAssociatedRecords(shape)) {
|
|
233
|
+
attachmentsProcessed = true
|
|
234
|
+
for (const rec of await shape.createAssociatedRecords({
|
|
235
|
+
parentRecord: record,
|
|
236
|
+
factory,
|
|
237
|
+
field,
|
|
238
|
+
})) {
|
|
239
|
+
const claimUpdateName = await transform.getUpdateName(rec)
|
|
240
|
+
const claimBuilder = recordBuilder.record(rec, claimUpdateName)
|
|
241
|
+
rec.entries()
|
|
242
|
+
.sort(([a], [b]) => a.localeCompare(b)) // Sort keys to make outputs more deterministic
|
|
243
|
+
.forEach(([prop, shape]) => claimBuilder.field(prop, shape))
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!attachmentsProcessed) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const claims = database
|
|
255
|
+
.query('sys_claim')
|
|
256
|
+
.filter((claim) => claim.get('metadata_update_name').equals(updateName))
|
|
257
|
+
|
|
258
|
+
for (const claim of claims) {
|
|
259
|
+
const claimUpdateName = await transform.getUpdateName(claim)
|
|
260
|
+
const claimBuilder = recordBuilder.record(claim, claimUpdateName)
|
|
261
|
+
claim
|
|
262
|
+
.entries()
|
|
263
|
+
.sort(([a], [b]) => a.localeCompare(b)) // Sort keys to make outputs more deterministic
|
|
264
|
+
.forEach(([prop, shape]) => claimBuilder.field(prop, shape))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
value: {
|
|
270
|
+
source: record,
|
|
271
|
+
name: `${updateName}.xml`,
|
|
272
|
+
category: record.getInstallCategory(),
|
|
273
|
+
content: recordBuilder.end(),
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
sys_attachment: {
|
|
279
|
+
toShape: async (record) => {
|
|
280
|
+
const fileName = record.get('file_name').getValue()
|
|
281
|
+
const hash = record.get('hash').getValue()
|
|
282
|
+
const extension = getExtension(record.get('content_type').toString().getValue() || '')
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
success: true,
|
|
286
|
+
value: new CallExpressionShape({
|
|
287
|
+
source: record,
|
|
288
|
+
args: [`${fileName}_${hash}${extension}`],
|
|
289
|
+
callee: 'Now.attach',
|
|
290
|
+
}),
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
shapes: [
|
|
296
|
+
{
|
|
297
|
+
shape: CallExpressionShape,
|
|
298
|
+
fileTypes: ['fluent'],
|
|
299
|
+
async toSubclass(callExpression, { diagnostics, project, fs }) {
|
|
300
|
+
if (callExpression.getCallee() !== 'Now.attach') {
|
|
301
|
+
return { success: false }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const arg = callExpression.getArgument(0)
|
|
305
|
+
if (!arg.isString()) {
|
|
306
|
+
diagnostics.error(arg, 'Now.attach() must have a string argument')
|
|
307
|
+
return { success: false }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const path = arg.getValue()
|
|
311
|
+
if (!/^\.\.?\/.*$/.test(path.trim()) && path.includes('/')) {
|
|
312
|
+
diagnostics.error(arg, 'Now.attach() argument must be a relative path')
|
|
313
|
+
return { success: false }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const absolutePath = pathModule.resolve(pathModule.dirname(callExpression.getOriginalFilePath()), path)
|
|
317
|
+
|
|
318
|
+
if (pathModule.relative(project.getRootDir(), absolutePath).startsWith('..')) {
|
|
319
|
+
diagnostics.error(arg, `File path is not within project: ${absolutePath}`)
|
|
320
|
+
return { success: false }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!FileSystem.existsSync(fs, absolutePath)) {
|
|
324
|
+
diagnostics.error(arg, 'File not found')
|
|
325
|
+
return { success: false }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const attachment = await NowAttachShape.create(callExpression.getSource(), fs, absolutePath)
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
value: attachment,
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
shape: NowAttachShape,
|
|
338
|
+
async commit(shape, target, { transform, fs }) {
|
|
339
|
+
const targetResult = await transform.toShape(target)
|
|
340
|
+
if (!targetResult.success) {
|
|
341
|
+
return { success: false }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const { value: targetShape } = targetResult
|
|
345
|
+
let absolutePath: string
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
targetShape.is(NowAttachShape) &&
|
|
349
|
+
path.extname(targetShape.getPath()) === path.extname(shape.getPath())
|
|
350
|
+
) {
|
|
351
|
+
absolutePath = targetShape.getPath()
|
|
352
|
+
} else {
|
|
353
|
+
absolutePath = path.resolve(path.dirname(target.getSourceFile().getFilePath()), shape.getPath())
|
|
354
|
+
target.replaceWithText(shape.getCode())
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true })
|
|
358
|
+
const bufferData = await shape.getBufferData()
|
|
359
|
+
if (!FileSystem.existsSync(fs, absolutePath) || !fs.readFileSync(absolutePath).equals(bufferData)) {
|
|
360
|
+
fs.writeFileSync(absolutePath, bufferData, { encoding: 'binary' })
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { success: true }
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
// The following is janky. The problem is, in the case of a transform where
|
|
367
|
+
// the ObjectLiteralExpression for a plugin CallExpression exists, but doesn't
|
|
368
|
+
// have the field yet (and will because it's coming from an incoming XML file),
|
|
369
|
+
// the commit code which saves the actual file (like say a .jpg file) is never
|
|
370
|
+
// called. Rather, the field is populated via getCode() instead which doesn't
|
|
371
|
+
// actually save the attached file. This commit is called before the basic-syntax-plugin's
|
|
372
|
+
// ObjectShape commiter, so we populate the Now.attach field so that the NowAttachPlugin's
|
|
373
|
+
// commit functiong gets called.
|
|
374
|
+
{
|
|
375
|
+
shape: ObjectShape,
|
|
376
|
+
async commit(shape, target) {
|
|
377
|
+
if (!ts.Node.isObjectLiteralExpression(target)) {
|
|
378
|
+
return { success: false }
|
|
379
|
+
}
|
|
380
|
+
const attachShapes = shape
|
|
381
|
+
.entries({ resolve: false })
|
|
382
|
+
.filter(([name, value]) => value.is(NowAttachShape) && !target.getProperty(name))
|
|
383
|
+
.map(([name, shape]) => {
|
|
384
|
+
return {
|
|
385
|
+
name: ObjectShape.quotePropertyNameIfNeeded(name),
|
|
386
|
+
initializer: shape.getCode(),
|
|
387
|
+
kind: ts.StructureKind.PropertyAssignment,
|
|
388
|
+
}
|
|
389
|
+
}) as ts.PropertyAssignmentStructure[]
|
|
390
|
+
|
|
391
|
+
if (attachShapes.length > 0) {
|
|
392
|
+
target.addPropertyAssignments(attachShapes)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return { success: false }
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
})
|
package/src/now-config-plugin.ts
CHANGED
|
@@ -130,6 +130,10 @@ export const NowConfigPlugin = Plugin.create({
|
|
|
130
130
|
return { success: false }
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
if (rawConfig.type === 'configuration') {
|
|
134
|
+
return { success: false }
|
|
135
|
+
}
|
|
136
|
+
|
|
133
137
|
const config = Shape.from(file.getJson(), rawConfig).asObject()
|
|
134
138
|
const scope = config.get('scope').asString().getValue()
|
|
135
139
|
|
|
@@ -139,8 +143,8 @@ export const NowConfigPlugin = Plugin.create({
|
|
|
139
143
|
let packageJsonPath: string
|
|
140
144
|
try {
|
|
141
145
|
packageJsonPath = NowConfig.moduleResolutionPath(rawConfig, packageJson, false, 'package.json')
|
|
142
|
-
} catch (e
|
|
143
|
-
diagnostics.error(file, e.message)
|
|
146
|
+
} catch (e) {
|
|
147
|
+
diagnostics.error(file, (e as Error).message)
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
const accessControls = config.get('accessControls').ifDefined()?.asObject()
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Shape,
|
|
10
10
|
FileSystem,
|
|
11
11
|
type Transform,
|
|
12
|
+
taxonomySchemaDefault,
|
|
12
13
|
} from '@servicenow/sdk-build-core'
|
|
13
14
|
import { CallExpressionPlugin } from './call-expression-plugin'
|
|
14
15
|
|
|
@@ -43,9 +44,15 @@ export class NowIncludeShape extends CallExpressionShape {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
static async fromRecord(record: Record, text: string | Shape, transform: Transform): Promise<NowIncludeShape> {
|
|
47
|
+
const scriptType = taxonomySchemaDefault[record.getTable()]
|
|
48
|
+
const scriptIdentifier = scriptType?.startsWith('client-development')
|
|
49
|
+
? '.client'
|
|
50
|
+
: scriptType?.startsWith('server-development')
|
|
51
|
+
? '.server'
|
|
52
|
+
: ''
|
|
46
53
|
return new NowIncludeShape({
|
|
47
54
|
source: record,
|
|
48
|
-
path: `./${await transform.getUpdateName(record)}.js`,
|
|
55
|
+
path: `./${await transform.getUpdateName(record)}${scriptIdentifier}.js`,
|
|
49
56
|
includedText: text instanceof Shape ? text.toString().getValue() : text,
|
|
50
57
|
})
|
|
51
58
|
}
|
|
@@ -16,7 +16,7 @@ export const PackageJsonPlugin = Plugin.create({
|
|
|
16
16
|
{
|
|
17
17
|
shape: JsonFileShape,
|
|
18
18
|
async toRecord(file, { config, packageJson, project, factory, fs, logger }) {
|
|
19
|
-
if (file.getBaseName() !== 'package.json') {
|
|
19
|
+
if (file.getBaseName() !== 'package.json' || config.type === 'configuration') {
|
|
20
20
|
return { success: false }
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -45,7 +45,7 @@ export const PackageJsonPlugin = Plugin.create({
|
|
|
45
45
|
path: sysModulePath,
|
|
46
46
|
content: Shape.from(
|
|
47
47
|
file,
|
|
48
|
-
await fixDependencyVersion(file.getJson(), {
|
|
48
|
+
await fixDependencyVersion(file.getJson().asObject(), {
|
|
49
49
|
fs,
|
|
50
50
|
logger,
|
|
51
51
|
rootDir: project.getRootDir(),
|
|
@@ -71,7 +71,7 @@ async function fixDependencyVersion(json: ObjectShape, context: { fs: FileSystem
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const repack = await RepackService.create(context.logger, context.fs, context.rootDir)
|
|
74
|
-
const resolvedDeps = {}
|
|
74
|
+
const resolvedDeps: { [key: string]: string } = {}
|
|
75
75
|
for (const dep of deps.keys()) {
|
|
76
76
|
const { name, version } = await repack.getResolvedDepDetails(dep)
|
|
77
77
|
resolvedDeps[name] = version
|