-
-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add a new command to filter the content of Profile and Pset based on multiple package.xml #90
Changes from 5 commits
7aad1fe
703a2c7
62021de
18ca344
c1da741
276e6f1
4df207a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module.exports = { | ||
command: 'prepare profile and permission', | ||
packagesFlagDescription: | ||
"package.xml paths to use to filter profile and permission set. Delimiter: '%s'", | ||
sourcesFlagDescription: | ||
"sources paths where to apply the filtering (use default if empty). Delimiter: '%s'", | ||
userPermissionsFlagDescription: | ||
"list of the userPermission to keep. Delimiter: '%s'", | ||
permissionsTypeFlagDescription: | ||
"list of the permission types to filter with the package <%s>. Delimiter: '%s'", | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import { flags, SfdxCommand } from '@salesforce/command' | ||
import { Messages, SfdxProject } from '@salesforce/core' | ||
import { AnyJson, JsonArray } from '@salesforce/ts-types' | ||
import { findInDir } from '../../../utils/findInDir' | ||
import * as gc from '../../../utils/gitConstants' | ||
import { | ||
XML_HEADER, | ||
XML_PARSER_OPTION, | ||
JSON_PARSER_OPTION, | ||
} from '../../../utils/parsingConstants' | ||
|
||
import * as fs from 'fs' | ||
import { XMLBuilder, XMLParser } from 'fast-xml-parser' | ||
import * as path from 'path' | ||
|
||
// Initialize Messages with the current plugin directory | ||
Messages.importMessagesDirectory(__dirname) | ||
const COMMAND_NAME = 'ppset' | ||
|
||
// Load the specific messages for this file. Messages from @salesforce/command, @salesforce/core, | ||
// or any library that is using the messages framework can also be loaded this way. | ||
const messages = Messages.loadMessages('sfdx-git-delta', COMMAND_NAME) | ||
const INPUT_DELIMITER = ':' | ||
|
||
const profilePackageMapping = { | ||
applicationVisibilities: { xmlTag: 'CustomApplication', key: 'application' }, | ||
categoryGroupVisibilities: { | ||
xmlTag: 'DataCategoryGroup', | ||
key: 'dataCategoryGroup', | ||
}, | ||
classAccesses: { xmlTag: 'ApexClass', key: 'apexClass' }, | ||
customMetadataTypeAccesses: { xmlTag: 'CustomMetadata', key: 'name' }, | ||
customPermissions: { xmlTag: 'CustomPermission', key: 'name' }, | ||
customSettingAccesses: { xmlTag: 'CustomObject', key: 'name' }, | ||
externalDataSourceAccesses: { | ||
xmlTag: 'ExternalDataSource', | ||
key: 'externalDataSource', | ||
}, | ||
fieldPermissions: { xmlTag: 'CustomField', key: 'field' }, | ||
layoutAssignments: { xmlTag: 'Layout', key: 'layout' }, // recordtype | ||
objectPermissions: { xmlTag: 'CustomObject', key: 'object' }, | ||
pageAccesses: { xmlTag: 'ApexPage', key: 'apexPage' }, | ||
recordTypeVisibilities: { xmlTag: 'RecordType', key: 'recordType' }, | ||
tabVisibilities: { xmlTag: 'CustomTab', key: 'tab' }, | ||
tabSettings: { xmlTag: 'CustomTab', key: 'tab' }, | ||
} | ||
|
||
const FILE_READ_OPTIONS = { | ||
encoding: gc.UTF8_ENCODING, | ||
} | ||
|
||
export default class Ppset extends SfdxCommand { | ||
public static description = messages.getMessage('command', []) | ||
|
||
protected static flagsConfig = { | ||
packages: flags.array({ | ||
char: 'p', | ||
description: messages.getMessage('packagesFlagDescription', [ | ||
INPUT_DELIMITER, | ||
]), | ||
delimiter: INPUT_DELIMITER, | ||
map: (val: string) => path.parse(val), | ||
required: true, | ||
}), | ||
sources: flags.array({ | ||
char: 's', | ||
description: messages.getMessage('sourcesFlagDescription', [ | ||
INPUT_DELIMITER, | ||
]), | ||
delimiter: INPUT_DELIMITER, | ||
map: (val: string) => path.parse(val), | ||
}), | ||
'permissions-type': flags.array({ | ||
char: 't', | ||
description: messages.getMessage('permissionsTypeFlagDescription', [ | ||
Object.keys(profilePackageMapping).join('|'), | ||
INPUT_DELIMITER, | ||
]), | ||
delimiter: INPUT_DELIMITER, | ||
}), | ||
'user-permissions': flags.array({ | ||
char: 'r', | ||
description: messages.getMessage('userPermissionsFlagDescription', [ | ||
INPUT_DELIMITER, | ||
]), | ||
delimiter: INPUT_DELIMITER, | ||
}), | ||
} | ||
|
||
protected static requiresProject = true | ||
|
||
public async run(): Promise<AnyJson> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function |
||
const project = await SfdxProject.resolve() | ||
const projectJson = await project.resolveProjectConfig() | ||
const basePath = project.getPath() | ||
const packageDirectories = projectJson['packageDirectories'] as JsonArray | ||
const defaultDir = packageDirectories.reduce( | ||
(a, v) => (v['default'] === true ? (a = v['path']) : (a = a)), | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'' | ||
) | ||
|
||
const sources = this.flags.sources || [] | ||
const userPermissions = this.flags['user-permissions'] || [] | ||
const dirList = packageDirectories.filter(dir => sources.includes(dir)) | ||
if (dirList.length === 0) { | ||
dirList.push(defaultDir) | ||
} | ||
|
||
const xmlParser = new XMLParser(XML_PARSER_OPTION) | ||
const xmlBuilder = new XMLBuilder(JSON_PARSER_OPTION) | ||
|
||
const packages = this.flags.packages.map(packageFile => | ||
xmlParser.parse( | ||
fs.readFileSync(path.format(packageFile), FILE_READ_OPTIONS) | ||
) | ||
) | ||
const allowedPermissions = this.flags['permissions-type'] || [] | ||
|
||
dirList.forEach(dir => | ||
findInDir( | ||
path.join(basePath, `${dir}`), | ||
/(?!permissionsetgroup)(\.profile)|(\.permissionset)/ | ||
) | ||
.filter(file => { | ||
const fileName = path.parse(file).name.split('.')[0] | ||
return ( | ||
packages.some(jsonObject => | ||
jsonObject?.Package[0]?.types | ||
.filter(x => x.name === 'Profile')[0] | ||
.members.includes(fileName) | ||
) || | ||
packages.some(jsonObject => | ||
jsonObject?.Package[0]?.types | ||
.filter(x => x.name === 'PermissionSet')[0] | ||
.members.includes(fileName) | ||
) | ||
) | ||
}) | ||
.forEach(file => { | ||
// Filter content based on the package.xml and the ppset | ||
const content = xmlParser.parse( | ||
fs.readFileSync(file, FILE_READ_OPTIONS) | ||
) | ||
const permissionContent = Object.values(content)[0][0] | ||
let authorizedKeys = Object.keys(permissionContent).filter(x => | ||
Object.keys(profilePackageMapping).includes(x) | ||
) | ||
if (allowedPermissions.length > 0) { | ||
authorizedKeys = authorizedKeys.filter(x => | ||
allowedPermissions.includes(x) | ||
) | ||
} | ||
|
||
authorizedKeys.forEach(permission => { | ||
const values = new Set() | ||
packages.forEach(jsonObject => | ||
jsonObject?.Package[0]?.types | ||
.filter( | ||
e => e.name === profilePackageMapping[permission].xmlTag | ||
) | ||
.forEach(element => | ||
Array.isArray(element.members) | ||
? element.members.forEach(member => values.add(member)) | ||
: values.add(element.members) | ||
) | ||
) | ||
permissionContent[permission] = permissionContent[ | ||
permission | ||
].filter(element => | ||
values.has(element[profilePackageMapping[permission].key]) | ||
) | ||
}) | ||
|
||
const inFileUserPermissions = | ||
permissionContent['userPermissions'] ?? [] | ||
permissionContent['userPermissions'] = | ||
userPermissions.length > 0 | ||
? inFileUserPermissions.filter(up => | ||
userPermissions.includes(up.name) | ||
) | ||
: [] | ||
const xmlContent = XML_HEADER + xmlBuilder.build(content) | ||
fs.writeFileSync(file, xmlContent) | ||
}) | ||
) | ||
return null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import * as fs from 'fs'; | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import * as path from 'path'; | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const findInDir = (dir: string, filter: RegExp, fileList: string[] = []): string[] => { | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const files = fs.readdirSync(dir); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
files.forEach(file => { | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const filePath = path.join(dir, file); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const fileStat = fs.lstatSync(filePath); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (fileStat.isDirectory()) { | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
findInDir(filePath, filter, fileList); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else if (filter.test(filePath)) { | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fileList.push(filePath); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return fileList; | ||
}; | ||
|
||
export { findInDir }; | ||
scolladon marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
'use strict' | ||
const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n' | ||
const XML_PARSER_OPTION = { | ||
ignoreAttributes: false, | ||
ignoreNameSpace: false, | ||
arrayMode: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can fix some potential issue in very edge case |
||
} | ||
const JSON_PARSER_OPTION = { | ||
...XML_PARSER_OPTION, | ||
format: true, | ||
indentBy: ' ', | ||
} | ||
module.exports.XML_HEADER = XML_HEADER | ||
module.exports.XML_PARSER_OPTION = XML_PARSER_OPTION | ||
module.exports.JSON_PARSER_OPTION = JSON_PARSER_OPTION |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function
run
has 88 lines of code (exceeds 25 allowed). Consider refactoring.