@ds-sfdc/sfparty 1.0.0-0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ds-sfdc/sfparty",
3
- "version": "1.0.0-0",
3
+ "version": "1.0.0",
4
4
  "description": "Salesforce metadata XML splitter for CI/CD",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,286 +0,0 @@
1
- import path from 'path'
2
- import os from 'os'
3
- import { readFileSync, writeFileSync, utimesSync } from 'fs'
4
- import logUpdate from 'log-update'
5
- import chalk from 'chalk'
6
- import convertHrtime from 'convert-hrtime'
7
- import cliSpinners from 'cli-spinners'
8
- import * as fileUtils from '../fileUtils.js'
9
- import { permsetDefinition } from '../../meta/PermissionSets.js'
10
- import * as xml2js from 'xml2js'
11
- import * as yaml from 'js-yaml'
12
-
13
- const spinner = cliSpinners['dots']
14
-
15
- export class Permset {
16
- #xml = ''
17
- #types = []
18
- #spinnerMessage = ''
19
- #index = 0
20
- #startTime = 0
21
- #fileName = ''
22
- #errorMessage = ''
23
- #fileStats
24
-
25
- constructor(config) {
26
- this.sourceDir = config.sourceDir
27
- this.targetDir = config.targetDir
28
- this.metaDir = config.metaDir
29
- this.sequence = config.sequence
30
- }
31
-
32
- combine() {
33
- return new Promise((resolve, reject) => {
34
- const that = this
35
- if (!fileUtils.directoryExists(path.join(that.sourceDir, that.metaDir))) reject(that.metaDir)
36
-
37
- that.metaDir = fileUtils.getDirectories(that.sourceDir).find(element => element.toLowerCase() == that.metaDir.toLowerCase())
38
-
39
- that.#xml = `<?xml version="1.0" encoding="UTF-8"?>${os.EOL}`
40
- that.#xml += `<PermissionSet xmlns="https://soap.sforce.com/2006/04/metadata">${os.EOL}`
41
-
42
- permsetDefinition.main.forEach(key => { that.#types.push(key) })
43
- permsetDefinition.singleFiles.forEach(key => { that.#types.push(key) })
44
- permsetDefinition.directories.forEach(key => { that.#types.push(key) })
45
- that.#types.sort()
46
-
47
- setFileName(that)
48
- processPermSet(that)
49
-
50
- saveXML(that)
51
- resolve(that.metaDir)
52
- })
53
-
54
- function setFileName(that) {
55
- const fileName = path.join(that.sourceDir, that.metaDir, `main.${global.format}`)
56
- if (fileUtils.fileExists(fileName)) {
57
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
58
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
59
- that.#fileStats = fileUtils.fileInfo(fileName).stats
60
- that.#fileName = path.join(that.targetDir, result.name + '.permissionset-meta.xml')
61
- }
62
- }
63
-
64
- function processPermSet(that) {
65
- that.#startTime = process.hrtime.bigint()
66
- that.#spinnerMessage = `[%1] of ${global.processed.total} - Permission Set: [%4]${chalk.yellowBright(that.metaDir)}[%2][%3]`
67
- logUpdate(that.#spinnerMessage
68
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
69
- .replace('[%2]', '')
70
- .replace('[%3]', '')
71
- .replace('[%4]', '')
72
- )
73
-
74
- that.#types.forEach(key => {
75
- let myLocation
76
- if (permsetDefinition.main.includes(key)) {
77
- myLocation = 'main'
78
- } else if (permsetDefinition.directories.includes(key)) {
79
- myLocation = 'directory'
80
- } else if (permsetDefinition.singleFiles.includes(key)) {
81
- myLocation = 'file'
82
- }
83
- logUpdate(that.#spinnerMessage
84
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
85
- .replace('[%2]', `\n${chalk.magentaBright(nextFrame(that))} ${key}`)
86
- .replace('[%3]', `${that.#errorMessage}`)
87
- .replace('[%4]', `${global.icons.working} `)
88
- )
89
-
90
- switch (myLocation) {
91
- case 'main':
92
- processMain(that, key)
93
- break
94
- case 'file':
95
- processFile(that, key)
96
- break
97
- case 'directory':
98
- processDirectory(that, key)
99
- break
100
- }
101
- })
102
- }
103
-
104
- function processMain(that, key) {
105
- const fileName = path.join(that.sourceDir, that.metaDir, `main.${global.format}`)
106
- if (fileUtils.fileExists(fileName)) {
107
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
108
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
109
- if (result[key] !== undefined) that.#xml += `\t<${key}>${result[key]}</${key}>${os.EOL}`
110
- }
111
- }
112
-
113
- function processFile(that, key) {
114
- const fileName = path.join(that.sourceDir, that.metaDir, `${key}.${global.format}`)
115
- if (fileUtils.fileExists(fileName)) {
116
- if (permsetDefinition.singleFiles.includes(key)) {
117
- genericXML(that, key)
118
- } else {
119
- that.#errorMessage += `\n${global.icons.warn} Not processed: ${key}`
120
- }
121
- }
122
- }
123
-
124
- function processDirectory(that, key) {
125
- switch (key) {
126
- case 'objectPermissions':
127
- objectPermissions(that, key)
128
- break
129
- default:
130
- if (permsetDefinition.directories.includes(key)) {
131
- genericDirectoryXML(that, key)
132
- break
133
- } else {
134
- that.#errorMessage += `\n${global.icons.warn} Not processed: ${key}`
135
- }
136
- }
137
-
138
- }
139
-
140
- function getTimeDiff(startTime, endTime = process.hrtime.bigint()) {
141
- const diff = BigInt(endTime) - BigInt(startTime)
142
- let executionTime = convertHrtime(diff)
143
- executionTime.seconds = Math.round(executionTime.seconds)
144
- executionTime.milliseconds = Math.round(executionTime.milliseconds / 1000)
145
- if (executionTime.milliseconds == 0 && executionTime.nanoseconds > 0) executionTime.milliseconds = 1
146
- return executionTime
147
- }
148
-
149
- function saveXML(that) {
150
- fileUtils.createDirectory(that.targetDir)
151
- that.#xml += '</PermissionSet>\n'
152
- writeFileSync(that.#fileName, that.#xml)
153
- utimesSync(that.#fileName, that.#fileStats.atime, that.#fileStats.mtime)
154
-
155
- let executionTime = getTimeDiff(BigInt(that.#startTime))
156
- let durationMessage = `${executionTime.seconds}.${executionTime.milliseconds}s`
157
- let stateIcon = (that.#errorMessage == '') ? global.icons.success : global.icons.fail
158
- logUpdate(that.#spinnerMessage
159
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
160
- .replace('[%2]', `. Processed in ${durationMessage}.`)
161
- .replace('[%3]', `${that.#errorMessage}`)
162
- .replace('[%4]', `${stateIcon} `)
163
- )
164
- logUpdate.done()
165
-
166
- }
167
-
168
- function nextFrame(that) {
169
- return spinner.frames[that.#index = ++that.#index % spinner.frames.length]
170
- }
171
-
172
- function genericXML(that, key) {
173
- const fileName = path.join(that.sourceDir, that.metaDir, `${key}.${global.format}`)
174
- const builder = new xml2js.Builder({ cdata: false, headless: true, rootName: key })
175
-
176
- if (fileUtils.fileExists(fileName)) {
177
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
178
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
179
- result[key] = sortJSONKeys(sortJSON(result[key], permsetDefinition.sortKeys[key]))
180
- result[key].forEach(element => {
181
- that.#xml += `\t<${key}>${os.EOL}`
182
- Object.keys(element).forEach(tag => {
183
- let xml
184
- try {
185
- xml = builder.buildObject(element[tag]).replace(`<${key}>`, '').replace(`</${key}>`, '')
186
- that.#xml += `\t\t<${tag}>${xml}</${tag}>${os.EOL}`
187
- } catch (error) {
188
- global.logger.error(error)
189
- }
190
- })
191
- that.#xml += `\t</${key}>${os.EOL}`
192
- })
193
- }
194
- }
195
-
196
- function genericDirectoryXML(that, key) {
197
- let dirPath = path.join(that.sourceDir, that.metaDir, key)
198
- if (!fileUtils.directoryExists(dirPath)) return
199
-
200
- let fileList = fileUtils.getFiles(dirPath, `.${global.format}`).sort()
201
- fileList.forEach(fileName => {
202
- const data = readFileSync(path.join(dirPath, fileName), { encoding: 'utf8', flag: 'r' })
203
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
204
- const object = result.object
205
- result[key] = sortJSONKeys(sortJSON(result[key], permsetDefinition.sortKeys[key]))
206
- result[key].forEach(element => {
207
- that.#xml += `\t<${key}>${os.EOL}`
208
- Object.keys(element).forEach(tag => {
209
- if (tag == permsetDefinition.sortKeys[key] && object) {
210
- that.#xml += `\t\t<${tag}>${object}.${element[tag]}</${tag}>${os.EOL}`
211
- } else {
212
- that.#xml += `\t\t<${tag}>${element[tag]}</${tag}>${os.EOL}`
213
- }
214
- })
215
- that.#xml += `\t</${key}>${os.EOL}`
216
- })
217
- })
218
- }
219
-
220
- function objectPermissions(that, key) {
221
- let dirPath = path.join(that.sourceDir, that.metaDir, key)
222
- if (!fileUtils.directoryExists(dirPath)) return
223
-
224
- let fileList = fileUtils.getFiles(dirPath, `.${global.format}`).sort((a, b) => a.localeCompare(b))
225
- fileList.forEach(fileName => {
226
- const data = readFileSync(path.join(dirPath, fileName), { encoding: 'utf8', flag: 'r' })
227
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
228
- result[key]['object'] = result.object
229
- result[key] = sortJSONKeys(result[key])
230
- that.#xml += `\t<${key}>${os.EOL}`
231
- Object.keys(result[key]).forEach(element => {
232
- that.#xml += `\t\t<${element}>${result[key][element]}</${element}>${os.EOL}`
233
- })
234
- that.#xml += `\t</${key}>${os.EOL}`
235
- })
236
- }
237
- // end of functions
238
- // end of combine
239
- }
240
-
241
- // end of class
242
- }
243
-
244
- function sortJSON(json, key) {
245
- if (Array.isArray(json)) {
246
- json.sort((a, b) => {
247
- if (a[key] < b[key]) return -1
248
- if (a[key] > b[key]) return 1
249
- return 0
250
- })
251
- }
252
- return json
253
- }
254
-
255
- function sortJSONKeys(json) {
256
- // sort json keys alphabetically
257
- if (Array.isArray(json)) {
258
- json.forEach(function (part, index) {
259
- this[index] = Object.keys(this[index])
260
- .sort((a, b) => {
261
- if (a < b) return -1
262
- if (a > b) return 1
263
- return 0
264
- })
265
- .reduce((accumulator, key) => {
266
- accumulator[key] = this[index][key]
267
-
268
- return accumulator
269
- }, {})
270
- }, json)
271
-
272
- } else {
273
- json = Object.keys(json)
274
- .sort((a, b) => {
275
- if (a < b) return -1
276
- if (a > b) return 1
277
- return 0
278
- })
279
- .reduce((accumulator, key) => {
280
- accumulator[key] = json[key]
281
-
282
- return accumulator
283
- }, {})
284
- }
285
- return json
286
- }
@@ -1,287 +0,0 @@
1
- 'use strict'
2
-
3
- import path from 'path'
4
- import fs from 'fs'
5
- import os from 'os'
6
- import { readFile } from 'fs'
7
- import { Parser } from 'xml2js'
8
- import logUpdate from 'log-update'
9
- import chalk from 'chalk'
10
- import convertHrtime from 'convert-hrtime'
11
- import cliSpinners from 'cli-spinners'
12
- import * as yaml from 'js-yaml'
13
- import * as fileUtils from '../fileUtils.js'
14
- import { permsetDefinition } from '../../meta/PermissionSets.js'
15
-
16
- const spinner = cliSpinners['dots']
17
-
18
- export class Permset {
19
- #fileName = {
20
- 'fullName': undefined,
21
- 'shortName': undefined,
22
- }
23
- #json
24
- #errorMessage = ''
25
- #index = 0
26
- #startTime = 0
27
- #spinnerMessage = ''
28
-
29
- constructor(config) {
30
- this.sourceDir = config.sourceDir
31
- this.targetDir = config.targetDir
32
- this.metaFilePath = config.metaFilePath
33
- this.sequence = config.sequence
34
- }
35
-
36
- get metaFilePath() {
37
- return this._metaFilePath
38
- }
39
-
40
- set metaFilePath(value) {
41
- value = value.trim()
42
- if (value === '') {
43
- throw 'The file path cannot be empty'
44
- }
45
- this._metaFilePath = value
46
- this.#fileName.fullName = fileUtils.fileInfo(value).filename
47
- this.#fileName.shortName = fileUtils.fileInfo(value).filename.replace('.permissionset-meta.xml', '')
48
- }
49
-
50
- split() {
51
- const that = this
52
- return new Promise((resolve, reject) => {
53
-
54
- if (!that.#fileName || !that.sourceDir || !that.targetDir || !that.metaFilePath) {
55
- global.logger.error('Invalid information passed to split')
56
- process.exit(1)
57
- }
58
- if (!fileUtils.fileExists(that.metaFilePath)) {
59
- global.logger.error(`file not found: ${that.metaFilePath}`)
60
- process.exit(1)
61
- }
62
-
63
- that.targetDir = path.join(that.targetDir, that.#fileName.shortName)
64
- let parser = new Parser()
65
- const getJSON = new Promise((resolve, reject) => {
66
- readFile(that.metaFilePath, function (err, data) {
67
- parser.parseString(data, function (err, result) {
68
- if (result) {
69
- resolve(result)
70
- } else {
71
- global.logger.error(`error converting xml to json: ${that.metaFilePath}`)
72
- process.exit(1)
73
- }
74
- })
75
- })
76
- })
77
- getJSON.then((result) => {
78
- // modify the json to remove unwanted arrays
79
- delete result.PermissionSet['$']
80
- let jsonString = JSON.stringify(result, (name, value) => {
81
- if (name == '' || !isNaN(name) || permsetDefinition.directories.includes(name) || permsetDefinition.singleFiles.includes(name)) {
82
- return value
83
- } else {
84
- return xml2json(value)
85
- }
86
- })
87
- that.#json = JSON.parse(jsonString)
88
-
89
- Object.keys(that.#json.PermissionSet).forEach(key => {
90
- const keyOrder = permsetDefinition.keyOrder[key]
91
- const sortKey = permsetDefinition.sortKeys[key]
92
-
93
- if (Array.isArray(that.#json.PermissionSet[key])) {
94
- // sort json to order by sortKey
95
- that.#json.PermissionSet[key].sort((a, b) => {
96
- if (a[sortKey] < b[sortKey]) {
97
- return -1;
98
- }
99
- if (a[sortKey] > b[sortKey]) {
100
- return 1;
101
- }
102
- return 0;
103
- })
104
-
105
- // sort json keys in specified order
106
- that.#json.PermissionSet[key].forEach(function (part, index) {
107
- this[index] = Object.keys(this[index])
108
- .sort((a, b) => {
109
- if (keyOrder.indexOf(a) < keyOrder.indexOf(b)) return -1
110
- if (keyOrder.indexOf(a) > keyOrder.indexOf(b)) return 1
111
- return 0
112
- })
113
- .reduce((accumulator, key) => {
114
- accumulator[key] = this[index][key]
115
- return accumulator
116
- }, {})
117
- }, that.#json.PermissionSet[key])
118
- }
119
- })
120
-
121
- processFile(that)
122
- completeFile(that)
123
- resolve(true)
124
- })
125
- })
126
-
127
- function nextFrame(that) {
128
- return spinner.frames[that.#index = ++that.#index % spinner.frames.length]
129
- }
130
-
131
- function completeFile(that) {
132
- let executionTime = getTimeDiff(BigInt(that.#startTime))
133
- let durationMessage = `${executionTime.seconds}.${executionTime.milliseconds}s`
134
- let stateIcon = (that.#errorMessage == '') ? global.icons.success : global.icons.fail
135
- logUpdate(that.#spinnerMessage
136
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
137
- .replace('[%2]', `. Processed in ${durationMessage}.`)
138
- .replace('[%3]', `${that.#errorMessage}`)
139
- .replace('[%4]', `${stateIcon} `)
140
- )
141
- logUpdate.done()
142
- }
143
-
144
- function processFile(that) {
145
- that.#startTime = process.hrtime.bigint()
146
- that.#spinnerMessage = `[%1] of ${global.processed.total} - Permission Set: [%4]${chalk.yellowBright(that.#fileName.shortName)}[%2][%3]`
147
-
148
- fileUtils.deleteDirectory(that.targetDir, true) // recursive delete existing directory
149
- fileUtils.createDirectory(that.targetDir) // create directory
150
-
151
- Main(that)
152
-
153
- Object.keys(that.#json.PermissionSet).forEach(key => {
154
- that.sequence = global.processed.current
155
- logUpdate(that.#spinnerMessage
156
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
157
- .replace('[%2]', `\n${chalk.magentaBright(nextFrame(that))} ${key}`)
158
- .replace('[%3]', `${that.#errorMessage}`)
159
- .replace('[%4]', `${global.icons.working} `)
160
- )
161
- if (permsetDefinition.directories.includes(key)) {
162
- processDirectory(that, key)
163
- } else if (permsetDefinition.singleFiles.includes(key)) {
164
- singleFile(that, key)
165
- } else {
166
- if (!permsetDefinition.ignore.includes(key) && !permsetDefinition.main.includes(key)) {
167
- that.#errorMessage += `\n${global.icons.warn} Not processed: ${key}`
168
- }
169
- }
170
- })
171
-
172
- return true
173
- }
174
-
175
- function Main(that) {
176
- let fileName = path.join(that.targetDir, `main.${global.format}`)
177
- let mainInfo = {}
178
- mainInfo.name = that.#fileName.shortName
179
- permsetDefinition.main.forEach(key => {
180
- if (that.#json.PermissionSet[key] !== undefined) {
181
- mainInfo[key] = that.#json.PermissionSet[key]
182
- }
183
- })
184
-
185
- let jsonString = JSON.stringify(mainInfo, null, '\t')
186
- switch (global.format) {
187
- case 'json':
188
- fs.writeFileSync(fileName, jsonString)
189
- break
190
- case 'yaml':
191
- let doc = yaml.dump(JSON.parse(jsonString))
192
- fs.writeFileSync(fileName, doc)
193
- }
194
- }
195
-
196
- function processDirectory(that, key) {
197
- const objects = {}
198
- const myKey = permsetDefinition.sortKeys[key]
199
- const hasObject = that.#json.PermissionSet[key][0][myKey].split('.').length == 2
200
- fileUtils.createDirectory(path.join(that.targetDir, key)) // create directory
201
-
202
- // populate objects with data per object
203
- if (hasObject) {
204
- that.#json.PermissionSet[key].forEach(element => {
205
- let [object] = element[myKey].toString().split('.')
206
- if (objects[object] === undefined) {
207
- objects[object] = {
208
- object: object
209
- }
210
- }
211
- if (objects[object][key] === undefined) {
212
- objects[object][key] = []
213
- }
214
- element[myKey] = element[myKey].replace(`${object}.`, '')
215
- objects[object][key].push(element)
216
- })
217
- } else {
218
- that.#json.PermissionSet[key].forEach(element => {
219
- let object = element[myKey]
220
- if (objects[object] === undefined) {
221
- objects[object] = {
222
- object: object
223
- }
224
- }
225
- if (objects[object][key] === undefined) {
226
- objects[object][key] = {}
227
- }
228
- delete element[myKey]
229
-
230
- Object.keys(element).forEach(elemKey => {
231
- objects[object][key][elemKey] = element[elemKey]
232
- })
233
- })
234
- }
235
-
236
- Object.keys(objects).forEach(object => {
237
- let fileName = path.join(that.targetDir, key, `${object}.${global.format}`)
238
-
239
- let jsonString = JSON.stringify(objects[object], null, '\t')
240
- switch (global.format) {
241
- case 'json':
242
- fs.writeFileSync(fileName, jsonString)
243
- break
244
- case 'yaml':
245
- let doc = yaml.dump(JSON.parse(jsonString))
246
- fs.writeFileSync(fileName, doc)
247
- }
248
- })
249
- }
250
-
251
- function singleFile(that, key) {
252
- let fileName = path.join(that.targetDir, `${key}.${global.format}`)
253
- let currentJSON = {}
254
- currentJSON[key] = that.#json.PermissionSet[key]
255
-
256
- let jsonString = JSON.stringify(currentJSON, null, '\t')
257
- switch (global.format) {
258
- case 'json':
259
- fs.writeFileSync(fileName, jsonString)
260
- break
261
- case 'yaml':
262
- let doc = yaml.dump(JSON.parse(jsonString))
263
- fs.writeFileSync(fileName, doc)
264
- }
265
- }
266
- }
267
- }
268
-
269
- function getTimeDiff(startTime, endTime = process.hrtime.bigint()) {
270
- const diff = BigInt(endTime) - BigInt(startTime)
271
- let executionTime = convertHrtime(diff)
272
- executionTime.seconds = Math.round(executionTime.seconds)
273
- executionTime.milliseconds = Math.round(executionTime.milliseconds / 1000)
274
- if (executionTime.milliseconds == 0 && executionTime.nanoseconds > 0) executionTime.milliseconds = 1
275
- return executionTime
276
- }
277
-
278
- function xml2json(currentValue) {
279
- if (Array.isArray(currentValue)) {
280
- if (currentValue.length == 1) {
281
- currentValue = currentValue[0].toString().trim()
282
- }
283
- }
284
- if (currentValue == 'true') currentValue = true
285
- if (currentValue == 'false') currentValue = false
286
- return currentValue
287
- }