@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.
Files changed (108) hide show
  1. package/dist/acl-plugin.js +13 -4
  2. package/dist/acl-plugin.js.map +1 -1
  3. package/dist/application-menu-plugin.js +1 -0
  4. package/dist/application-menu-plugin.js.map +1 -1
  5. package/dist/atf/step-configs.d.ts +13 -12
  6. package/dist/atf/step-configs.js.map +1 -1
  7. package/dist/atf/test-plugin.d.ts +1 -1
  8. package/dist/atf/test-plugin.js +8 -5
  9. package/dist/atf/test-plugin.js.map +1 -1
  10. package/dist/basic-syntax-plugin.js +51 -13
  11. package/dist/basic-syntax-plugin.js.map +1 -1
  12. package/dist/business-rule-plugin.js.map +1 -1
  13. package/dist/claims-plugin.js +1 -1
  14. package/dist/claims-plugin.js.map +1 -1
  15. package/dist/client-script-plugin.js +5 -17
  16. package/dist/client-script-plugin.js.map +1 -1
  17. package/dist/column/column-helper.d.ts +1 -1
  18. package/dist/column/column-helper.js +46 -2
  19. package/dist/column/column-helper.js.map +1 -1
  20. package/dist/column/column-to-record.js +6 -4
  21. package/dist/column/column-to-record.js.map +1 -1
  22. package/dist/column-plugin.js +106 -27
  23. package/dist/column-plugin.js.map +1 -1
  24. package/dist/data-plugin.d.ts +3 -0
  25. package/dist/data-plugin.js +208 -0
  26. package/dist/data-plugin.js.map +1 -0
  27. package/dist/import-sets-plugin.d.ts +2 -0
  28. package/dist/import-sets-plugin.js +412 -0
  29. package/dist/import-sets-plugin.js.map +1 -0
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.js +4 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/json-plugin.d.ts +4 -4
  34. package/dist/json-plugin.js +21 -7
  35. package/dist/json-plugin.js.map +1 -1
  36. package/dist/list-plugin.js +83 -1
  37. package/dist/list-plugin.js.map +1 -1
  38. package/dist/now-attach-plugin.d.ts +35 -0
  39. package/dist/now-attach-plugin.js +317 -0
  40. package/dist/now-attach-plugin.js.map +1 -0
  41. package/dist/now-config-plugin.js +3 -0
  42. package/dist/now-config-plugin.js.map +1 -1
  43. package/dist/now-include-plugin.js +7 -1
  44. package/dist/now-include-plugin.js.map +1 -1
  45. package/dist/package-json-plugin.js +2 -2
  46. package/dist/package-json-plugin.js.map +1 -1
  47. package/dist/record-plugin.d.ts +6 -0
  48. package/dist/record-plugin.js +50 -23
  49. package/dist/record-plugin.js.map +1 -1
  50. package/dist/repack/lint/Rules.js.map +1 -1
  51. package/dist/rest-api-plugin.js +28 -31
  52. package/dist/rest-api-plugin.js.map +1 -1
  53. package/dist/role-plugin.js +1 -0
  54. package/dist/role-plugin.js.map +1 -1
  55. package/dist/server-module-plugin/index.js +15 -2
  56. package/dist/server-module-plugin/index.js.map +1 -1
  57. package/dist/service-portal/widget-plugin.js +4 -1
  58. package/dist/service-portal/widget-plugin.js.map +1 -1
  59. package/dist/static-content-plugin.d.ts +1 -0
  60. package/dist/static-content-plugin.js +4 -3
  61. package/dist/static-content-plugin.js.map +1 -1
  62. package/dist/table-plugin.js +33 -2
  63. package/dist/table-plugin.js.map +1 -1
  64. package/dist/ui-page-plugin.js +2 -1
  65. package/dist/ui-page-plugin.js.map +1 -1
  66. package/dist/ui-policy-plugin.d.ts +2 -0
  67. package/dist/ui-policy-plugin.js +407 -0
  68. package/dist/ui-policy-plugin.js.map +1 -0
  69. package/dist/utils.d.ts +10 -1
  70. package/dist/utils.js +24 -0
  71. package/dist/utils.js.map +1 -1
  72. package/dist/view-plugin.js +1 -1
  73. package/dist/view-plugin.js.map +1 -1
  74. package/package.json +11 -7
  75. package/src/_types/eslint-plugin-es-x.d.ts +17 -0
  76. package/src/_types/md5.js.d.ts +8 -0
  77. package/src/acl-plugin.ts +19 -9
  78. package/src/application-menu-plugin.ts +1 -0
  79. package/src/atf/step-configs.ts +14 -12
  80. package/src/atf/test-plugin.ts +40 -21
  81. package/src/basic-syntax-plugin.ts +61 -13
  82. package/src/business-rule-plugin.ts +7 -4
  83. package/src/claims-plugin.ts +1 -1
  84. package/src/client-script-plugin.ts +8 -22
  85. package/src/column/column-helper.ts +65 -3
  86. package/src/column/column-to-record.ts +6 -4
  87. package/src/column-plugin.ts +141 -39
  88. package/src/data-plugin.ts +266 -0
  89. package/src/import-sets-plugin.ts +542 -0
  90. package/src/index.ts +4 -0
  91. package/src/json-plugin.ts +31 -12
  92. package/src/list-plugin.ts +91 -1
  93. package/src/now-attach-plugin.ts +399 -0
  94. package/src/now-config-plugin.ts +6 -2
  95. package/src/now-include-plugin.ts +8 -1
  96. package/src/package-json-plugin.ts +3 -3
  97. package/src/record-plugin.ts +61 -30
  98. package/src/repack/lint/Rules.ts +1 -10
  99. package/src/rest-api-plugin.ts +45 -51
  100. package/src/role-plugin.ts +1 -0
  101. package/src/server-module-plugin/index.ts +21 -5
  102. package/src/service-portal/widget-plugin.ts +4 -1
  103. package/src/static-content-plugin.ts +2 -2
  104. package/src/table-plugin.ts +47 -7
  105. package/src/ui-page-plugin.ts +2 -1
  106. package/src/ui-policy-plugin.ts +509 -0
  107. package/src/utils.ts +27 -1
  108. package/src/view-plugin.ts +1 -1
@@ -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: `sys_ui_list_${list.getId().getValue()}.xml`,
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
+ })
@@ -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: any) {
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