@ds-sfdc/sfparty 1.0.0-0 → 1.1.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.
@@ -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
- }
@@ -1,309 +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 { profileDefinition } from '../../meta/Profiles.js'
10
- import * as xml2js from 'xml2js'
11
- import * as yaml from 'js-yaml'
12
-
13
- const spinner = cliSpinners['dots']
14
-
15
- export class Profile {
16
- #xml = ''
17
- #types = []
18
- #spinnerMessage = ''
19
- #index = 0
20
- #startTime = 0
21
- #fileName = ''
22
- #errorMessage = ''
23
- #fileStats
24
- #root = 'Profile'
25
-
26
- constructor(config) {
27
- this.sourceDir = config.sourceDir
28
- this.targetDir = config.targetDir
29
- this.metaDir = config.metaDir
30
- this.sequence = config.sequence
31
- }
32
-
33
- combine() {
34
- return new Promise((resolve, reject) => {
35
- const that = this
36
- if (!fileUtils.directoryExists(path.join(that.sourceDir, that.metaDir))) reject(that.metaDir)
37
-
38
- that.metaDir = fileUtils.getDirectories(that.sourceDir).find(element => element.toLowerCase() == that.metaDir.toLowerCase())
39
-
40
- that.#xml = `<?xml version="1.0" encoding="UTF-8"?>${os.EOL}`
41
- that.#xml += `<Profile xmlns="https://soap.sforce.com/2006/04/metadata">${os.EOL}`
42
-
43
- profileDefinition.main.forEach(key => { that.#types.push(key) })
44
- profileDefinition.singleFiles.forEach(key => { that.#types.push(key) })
45
- profileDefinition.directories.forEach(key => { that.#types.push(key) })
46
- that.#types.sort()
47
-
48
- setFileName(that)
49
- processProfile(that)
50
-
51
- saveXML(that)
52
- resolve(that.metaDir)
53
- })
54
-
55
- function setFileName(that) {
56
- const fileName = path.join(that.sourceDir, that.metaDir, `main.${global.format}`)
57
- if (fileUtils.fileExists(fileName)) {
58
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
59
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
60
- that.#fileStats = fileUtils.fileInfo(fileName).stats
61
- that.#fileName = path.join(that.targetDir, result.name + '.profile-meta.xml')
62
- }
63
- }
64
-
65
- function processProfile(that) {
66
- that.#startTime = process.hrtime.bigint()
67
- that.#spinnerMessage = `[%1] of ${global.processed.total} - Profile: [%4]${chalk.yellowBright(that.metaDir)}[%2][%3]`
68
- logUpdate(that.#spinnerMessage
69
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
70
- .replace('[%2]', '')
71
- .replace('[%3]', '')
72
- .replace('[%4]', '')
73
- )
74
-
75
- that.#types.forEach(key => {
76
- let myLocation
77
- if (profileDefinition.main.includes(key)) {
78
- myLocation = 'main'
79
- } else if (profileDefinition.directories.includes(key)) {
80
- myLocation = 'directory'
81
- } else if (profileDefinition.singleFiles.includes(key)) {
82
- myLocation = 'file'
83
- }
84
- logUpdate(that.#spinnerMessage
85
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
86
- .replace('[%2]', `\n${chalk.magentaBright(nextFrame(that))} ${key}`)
87
- .replace('[%3]', `${that.#errorMessage}`)
88
- .replace('[%4]', `${global.icons.working} `)
89
- )
90
-
91
- switch (myLocation) {
92
- case 'main':
93
- processMain(that, key)
94
- break
95
- case 'file':
96
- processFile(that, key)
97
- break
98
- case 'directory':
99
- processDirectory(that, key)
100
- break
101
- }
102
- })
103
- }
104
-
105
- function processMain(that, key) {
106
- const fileName = path.join(that.sourceDir, that.metaDir, `main.${global.format}`)
107
- if (fileUtils.fileExists(fileName)) {
108
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
109
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
110
- if (result[key] !== undefined) that.#xml += `\t<${key}>${result[key]}</${key}>${os.EOL}`
111
- }
112
- }
113
-
114
- function processFile(that, key) {
115
- const fileName = path.join(that.sourceDir, that.metaDir, `${key}.${global.format}`)
116
- if (fileUtils.fileExists(fileName)) {
117
- switch (key) {
118
- case 'categoryGroupVisibilities':
119
- categoryGroupVisibilities(that, key)
120
- break
121
- // TODO case 'loginHours':
122
- default:
123
- if (profileDefinition.singleFiles.includes(key)) {
124
- genericXML(that, key)
125
- break
126
- } else {
127
- that.#errorMessage += `\n${global.icons.warn} Not processed: ${key}`
128
- }
129
- }
130
- }
131
- }
132
-
133
- function processDirectory(that, key) {
134
- switch (key) {
135
- case 'objectPermissions':
136
- objectPermissions(that, key)
137
- break
138
- default:
139
- if (profileDefinition.directories.includes(key)) {
140
- genericDirectoryXML(that, key)
141
- break
142
- } else {
143
- that.#errorMessage += `\n${global.icons.warn} Not processed: ${key}`
144
- }
145
- }
146
-
147
- }
148
-
149
- function getTimeDiff(startTime, endTime = process.hrtime.bigint()) {
150
- const diff = BigInt(endTime) - BigInt(startTime)
151
- let executionTime = convertHrtime(diff)
152
- executionTime.seconds = Math.round(executionTime.seconds)
153
- executionTime.milliseconds = Math.round(executionTime.milliseconds / 1000)
154
- if (executionTime.milliseconds == 0 && executionTime.nanoseconds > 0) executionTime.milliseconds = 1
155
- return executionTime
156
- }
157
-
158
- function saveXML(that) {
159
- fileUtils.createDirectory(that.targetDir)
160
- that.#xml += '</Profile>\n'
161
- writeFileSync(that.#fileName, that.#xml)
162
- utimesSync(that.#fileName, that.#fileStats.atime, that.#fileStats.mtime)
163
-
164
- let executionTime = getTimeDiff(BigInt(that.#startTime))
165
- let durationMessage = `${executionTime.seconds}.${executionTime.milliseconds}s`
166
- let stateIcon = (that.#errorMessage == '') ? global.icons.success : global.icons.fail
167
- logUpdate(that.#spinnerMessage
168
- .replace('[%1]', that.sequence.toString().padStart(global.processed.total.toString().length, ' '))
169
- .replace('[%2]', `. Processed in ${durationMessage}.`)
170
- .replace('[%3]', `${that.#errorMessage}`)
171
- .replace('[%4]', `${stateIcon} `)
172
- )
173
- logUpdate.done()
174
-
175
- }
176
-
177
- function nextFrame(that) {
178
- return spinner.frames[that.#index = ++that.#index % spinner.frames.length]
179
- }
180
-
181
- function genericXML(that, key) {
182
- const fileName = path.join(that.sourceDir, that.metaDir, `${key}.${global.format}`)
183
- const builder = new xml2js.Builder({ cdata: false, headless: true, rootName: that.#root })
184
-
185
- if (fileUtils.fileExists(fileName)) {
186
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
187
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
188
- result[key] = sortJSONKeys(sortJSON(result[key], profileDefinition.sortKeys[key]))
189
- that.#xml += builder.buildObject(result)
190
- .replace(`<${that.#root}>${os.EOL}`, '')
191
- .replace(`</${that.#root}>`, '')
192
- }
193
- }
194
-
195
- function genericDirectoryXML(that, key) {
196
- let dirPath = path.join(that.sourceDir, that.metaDir, key)
197
- if (!fileUtils.directoryExists(dirPath)) return
198
-
199
- let fileList = fileUtils.getFiles(dirPath, `.${global.format}`).sort()
200
- fileList.forEach(fileName => {
201
- const builder = new xml2js.Builder({ cdata: false, headless: true, rootName: that.#root })
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], profileDefinition.sortKeys[key]))
206
- result[key].forEach(element => {
207
- Object.keys(element).forEach(tag => {
208
- if (tag == profileDefinition.sortKeys[key] && object) {
209
- [element][tag] = `${object}.${element[tag]}`
210
- }
211
- })
212
- that.#xml += builder.buildObject(result)
213
- .replace(`<${that.#root}>${os.EOL}`, '')
214
- .replace(`</${that.#root}>`, '')
215
- })
216
-
217
- })
218
- }
219
-
220
- function categoryGroupVisibilities(that, key) {
221
- const fileName = path.join(that.sourceDir, that.metaDir, `${key}.${global.format}`)
222
- if (fileUtils.fileExists(fileName)) {
223
- const data = readFileSync(fileName, { encoding: 'utf8', flag: 'r' })
224
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
225
- result[key] = sortJSONKeys(sortJSON(result[key], profileDefinition.sortKeys[key]))
226
- result[key].forEach(element => {
227
- that.#xml += `\t<${key}>${os.EOL}`
228
- Object.keys(element).forEach(tag => {
229
- // dataCategories is an array of values which must be handled separately
230
- if (tag == 'dataCategories') {
231
- sortJSON(element.dataCategories).forEach(category => {
232
- that.#xml += `\t\t<${tag}>${category}</${tag}>${os.EOL}`
233
- })
234
- } else {
235
- that.#xml += `\t\t<${tag}>${element[tag]}</${tag}>${os.EOL}`
236
- }
237
- })
238
- that.#xml += `\t</${key}>${os.EOL}`
239
- })
240
- }
241
- }
242
-
243
- function objectPermissions(that, key) {
244
- let dirPath = path.join(that.sourceDir, that.metaDir, key)
245
- if (!fileUtils.directoryExists(dirPath)) return
246
-
247
- let fileList = fileUtils.getFiles(dirPath, `.${global.format}`).sort((a, b) => a.localeCompare(b))
248
- fileList.forEach(fileName => {
249
- const data = readFileSync(path.join(dirPath, fileName), { encoding: 'utf8', flag: 'r' })
250
- const result = (global.format == 'yaml') ? yaml.load(data) : JSON.parse(data)
251
- result[key]['object'] = result.object
252
- result[key] = sortJSONKeys(result[key])
253
- that.#xml += `\t<${key}>${os.EOL}`
254
- Object.keys(result[key]).forEach(element => {
255
- that.#xml += `\t\t<${element}>${result[key][element]}</${element}>${os.EOL}`
256
- })
257
- that.#xml += `\t</${key}>${os.EOL}`
258
- })
259
- }
260
- // end of functions
261
- // end of combine
262
- }
263
-
264
- // end of class
265
- }
266
-
267
- function sortJSON(json, key) {
268
- if (Array.isArray(json)) {
269
- json.sort((a, b) => {
270
- if (a[key] < b[key]) return -1
271
- if (a[key] > b[key]) return 1
272
- return 0
273
- })
274
- }
275
- return json
276
- }
277
-
278
- function sortJSONKeys(json) {
279
- // sort json keys alphabetically
280
- if (Array.isArray(json)) {
281
- json.forEach(function (part, index) {
282
- this[index] = Object.keys(this[index])
283
- .sort((a, b) => {
284
- if (a < b) return -1
285
- if (a > b) return 1
286
- return 0
287
- })
288
- .reduce((accumulator, key) => {
289
- accumulator[key] = this[index][key]
290
-
291
- return accumulator
292
- }, {})
293
- }, json)
294
-
295
- } else {
296
- json = Object.keys(json)
297
- .sort((a, b) => {
298
- if (a < b) return -1
299
- if (a > b) return 1
300
- return 0
301
- })
302
- .reduce((accumulator, key) => {
303
- accumulator[key] = json[key]
304
-
305
- return accumulator
306
- }, {})
307
- }
308
- return json
309
- }