import { flags , SfdxCommand } from '@salesforce/command' ; import { JsonArray , JsonMap } from '@salesforce/ts-types' ; import { Messages , SfdxProjectJson , SfdxError } from '@salesforce/core' ; import * as fs from 'fs' ; import * as path from 'path' ; const util = require ( 'util' ) ; const xml2js = require ( 'xml2js' ) ; Messages . importMessagesDirectory ( __dirname ) ; const messages = Messages . loadMessages ( 'texei-sfdx-plugin' , 'profile-clean' ) ; const defaultProfileFolder = 'force-app/main/default/profiles' ; export default class Clean extends SfdxCommand { public static description = messages . getMessage ( 'commandDescription' ) ; public static examples = [ '$ texei:profile:clean -k layoutAssignments,recordTypeVisibilities' , '$ texei:profile:clean -p custom-sfdx-source-folder/main/profiles' , '$ texei:profile:clean -p custom-sfdx-source-folder/main/profiles,source-folder-2/main/profiles/myAdmin.profile-meta.xml' ] ; protected static flagsConfig = { keep : flags . string ( { char : 'k' , required : false , description : 'comma-separated list of profile node permissions that need to be kept. Default: layoutAssignments,loginHours,loginIpRanges,custom,userLicense' } ) , path : flags . string ( { char : 'p' , required : false , description : 'comma-separated list of profiles, or path to profiles folder. Default: default package directory' } ) } ; protected static requiresUsername = false ; protected static requiresDevhubUsername = false ; protected static requiresProject = false ; public async run ( ) { let cleanResult = [ ] ; const defaultKeep = [ 'layoutAssignments' , 'loginHours' , 'loginIpRanges' , 'custom' , 'userLicense' ] ; const nodesToKeep = this . flags . keep ? this . flags . keep : defaultKeep ; let profilesToClean = [ ] ; if ( this . flags . path ) { const paths = this . flags . path . split ( ',' ) ; for ( const currentPath of paths ) { if ( currentPath . endsWith ( '.profile-meta.xml' ) ) { profilesToClean . push ( currentPath ) ; } else { profilesToClean = await this . getProfilesInPath ( currentPath ) ; } } } else { const defaultPackageDirectory = await this . getDefaultPath ( ) ; profilesToClean = await this . getProfilesInPath ( defaultPackageDirectory ) ; } if ( profilesToClean . length == 0 ) { this . ux . log ( 'No Profile found :(' ) ; } const readFile = util . promisify ( fs . readFile ) ; for ( const profilePath of profilesToClean ) { const filePath = path . join ( process . cwd ( ) , profilePath ) ; const data = await readFile ( filePath , 'utf8' ) ; var parser = new xml2js . Parser ( ) ; const parseString = util . promisify ( parser . parseString ) ; const profileJson = await parseString ( data ) ; for ( const nodeKey in profileJson . Profile ) { if ( profileJson . Profile . hasOwnProperty ( nodeKey ) ) { if ( ! nodesToKeep . includes ( nodeKey ) ) { delete profileJson . Profile [ nodeKey ] ; } } } const builder = new xml2js . Builder ( ) ; var xmlFile = builder . buildObject ( profileJson ) ; await fs . writeFile ( filePath , xmlFile , 'utf8' , function ( err ) { if ( err ) { throw new SfdxError ( `Unable to write Profile file at path ${ filePath }: ${ err }` ) ; } } ) ; this . ux . log ( `Profile cleaned: ${ profilePath }` ) ; cleanResult . push ( profilePath ) ; } return { profilesCleaned : cleanResult } ; } private async getProfilesInPath ( pathToRead ) { let profilesInPath = [ ] ; const readDirectory = util . promisify ( fs . readdir ) ; const filesInDir = await readDirectory ( pathToRead ) ; for ( const fileInDir of filesInDir ) { const dirOrFilePath = path . join ( process . cwd ( ) , pathToRead , fileInDir ) ; if ( ! fs . lstatSync ( dirOrFilePath ) . isDirectory ( ) && fileInDir . endsWith ( '.profile-meta.xml' ) ) { const profileFoundPath = path . join ( pathToRead , fileInDir ) ; profilesInPath . push ( profileFoundPath ) ; } } return profilesInPath ; } private async getDefaultPath ( ) { const options = SfdxProjectJson . getDefaultOptions ( ) ; const project = await SfdxProjectJson . create ( options ) ; const packageDirectories = project . get ( 'packageDirectories' ) as JsonArray || [ ] ; let foundPath ; for ( let packageDirectory of packageDirectories ) { packageDirectory = packageDirectory as JsonMap ; if ( packageDirectory . path && packageDirectory . default ) { foundPath = path . join ( packageDirectory . path as string , 'main' , 'default' , 'profiles' ) ; break ; } if ( ! foundPath ) { foundPath = defaultProfileFolder ; } } return foundPath ; } }