import { SfdxCommand , flags } from '@salesforce/command' ; import { Messages , SfdxError } from '@salesforce/core' ; import * as fs from 'fs' ; import * as path from 'path' ; import { toTitleCase } from "../../../../shared/utils" ; Messages . importMessagesDirectory ( __dirname ) ; const messages = Messages . loadMessages ( 'texei-sfdx-plugin' , 'org-shape-extract' ) ; const definitionFileName = 'project-scratch-def.json' ; const settingValuesProdOnly = [ 'Packaging2' , 'ExpandedSourceTrackingPref' , 'ScratchOrgManagementPref' , 'ShapeExportPref' , 'PRMAccRelPref' ] ; const settingValuesBugsRelated = [ 'enableOmniAutoLoginPrompt' , 'enableOmniSecondaryRoutingPriority' , 'VoiceCallListEnabled' , 'VoiceCallRecordingEnabled' , 'VoiceCoachingEnabled' , 'VoiceConferencingEnabled' , 'VoiceEnabled' , 'VoiceLocalPresenceEnabled' , 'VoiceMailDropEnabled' , 'VoiceMailEnabled' , 'CallDispositionEnabled' ] ; const settingValuesBugsToInvestigate = [ 'enableEngagementHistoryDashboards' , 'EventLogWaveIntegEnabled' , 'SendThroughGmailPref' , 'PardotAppV1Enabled' , 'PardotEmbeddedAnalyticsPref' , 'PardotEnabled' , 'allowUsersToRelateMultipleContactsToTasksAndEvents' , 'socialCustomerServiceSettings' , 'opportunityFilterSettings' , 'enableAccountOwnerReport' , 'defaultCaseOwner' , 'PortalUserShareOnCase' , 'keepRecordTypeOnAssignmentRule' , 'webToCase' , 'routingAddresses' ] ; const featureDependencies = new Map < string , String > ( [ [ 'enableCommunityWorkspaces' , 'Communities' ] ] ) ; export default class Extract extends SfdxCommand { public static description = messages . getMessage ( 'commandDescription' ) ; public static examples = [ `$ sfdx texei:org:shape:extract -u bulma@capsulecorp.com` ] ; protected static flagsConfig = { outputdir : flags . string ( { char : 'd' , description : messages . getMessage ( 'directoryFlagDescription' ) , default : 'config' } ) , scope : flags . string ( { char : 's' , description : messages . getMessage ( 'scopeFlagDescription' ) , options : [ 'basic' , 'full' , 'shaperepresentation' ] , default : 'basic' } ) } ; protected static requiresUsername = true ; protected static requiresDevhubUsername = false ; protected static requiresProject = false ; public async run ( ) { this . ux . warn ( 'This command is in beta, only extracting some settings. Read more at https://github.com/texei/texei-sfdx-plugin/blob/master/org-shape-command.md' ) ; this . ux . startSpinner ( 'Extracting Org Shape' , null , { stdout : true } ) ; const query = 'Select Name, Country, LanguageLocaleKey, OrganizationType from Organization' ; const conn = this . org . getConnection ( ) ; const orgInfos = await conn . query ( query ) as any ; let featureList = [ ] ; let definitionValues = { } ; let definitionValuesTemp = { } ; definitionValuesTemp . settings = { } ; const settingValuesToIgnore = ( this . flags . scope === 'full' ) ? [ ] : settingValuesProdOnly . concat ( settingValuesBugsRelated ) . concat ( settingValuesBugsToInvestigate ) ; let apiVersion = this . flags . apiversion ; if ( ! apiVersion ) { apiVersion = await this . org . retrieveMaxApiVersion ( ) ; } if ( this . flags . scope === 'shaperepresentation' ) { const shapeQuery = `Select Edition, Features, Settings from ShapeRepresentation where Status = 'Active'` ; try { const shapeRepresentation = ( await conn . query ( shapeQuery ) as any ) . records [ 0 ] ; definitionValues . orgName = orgInfos . records [ 0 ] . Name ; if ( shapeRepresentation . Edition ) { definitionValues . edition = await toTitleCase ( shapeRepresentation . Edition ) ; } definitionValues . language = orgInfos . records [ 0 ] . LanguageLocaleKey ; if ( shapeRepresentation . Features ) { definitionValues . features = shapeRepresentation . Features . split ( ';' ) ; } if ( shapeRepresentation . Settings ) { let shapeSettings = JSON . parse ( shapeRepresentation . Settings ) ; delete shapeSettings . devHubSettings ; definitionValues . settings = shapeSettings ; } } catch ( ex ) { throw new SfdxError ( 'Unable to query ShapeRepresentation, make sure to target an org with Org Shape enabled and a shape created.' ) ; } } else { const settingPromises = [ ] ; var types = [ { type : 'Settings' , folder : null } ] ; await conn . metadata . list ( types , apiVersion , function ( err , metadata ) { if ( err ) { return console . error ( 'err' , err ) ; } for ( let meta of metadata ) { const settingType = meta . fullName + meta . type ; const settingPromise = conn . metadata . read ( settingType , settingType ) ; settingPromises . push ( settingPromise ) ; } } ) ; await Promise . all ( settingPromises ) . then ( ( settingValues ) => { for ( const setting of settingValues ) { if ( setting . fullName == 'OrgPreferenceSettings' ) { const settingsName = this . toLowerCamelCase ( setting . fullName ) ; let settingValues = { } ; for ( const subsetting of setting . preferences ) { if ( ! settingValuesToIgnore . includes ( subsetting . settingName ) ) { const settingName = this . toLowerCamelCase ( subsetting . settingName ) ; settingValues [ settingName ] = subsetting . settingValue ; if ( featureDependencies . has ( settingName ) ) { featureList . push ( featureDependencies . get ( settingName ) ) ; } } } definitionValuesTemp . settings [ settingsName ] = settingValues ; } const settingsToTest = [ 'AccountSettings' , 'ActivitiesSettings' , 'AddressSettings' , 'BusinessHoursSettings' , 'CaseSettings' , 'CommunitiesSettings' , 'CompanySettings' , 'ContractSettings' , 'EntitlementSettings' , 'FileUploadAndDownloadSecuritySettings' , 'IdeasSettings' , 'MacroSettings' , 'MobileSettings' , 'NameSettings' , 'OmniChannelSettings' , 'OpportunitySettings' , 'OrderSettings' , 'PathAssistantSettings' , 'ProductSettings' , 'QuoteSettings' , 'SecuritySettings' , 'SocialCustomerServiceSettings' ] ; if ( setting . fullName !== undefined && ( settingsToTest . includes ( setting . fullName ) || this . flags . scope === 'full' ) ) { const settingName = this . toLowerCamelCase ( setting . fullName ) ; if ( ! settingValuesToIgnore . includes ( settingName ) ) { const formattedSetting = this . formatSetting ( setting ) ; for ( const property in setting ) { if ( featureDependencies . has ( property ) ) { featureList . push ( featureDependencies . get ( property ) ) ; } if ( setting . hasOwnProperty ( property ) && settingValuesToIgnore . includes ( property ) ) { delete setting [ property ] ; } for ( const prop in setting [ property ] ) { if ( setting . hasOwnProperty ( property ) && setting [ property ] . hasOwnProperty ( prop ) && settingValuesToIgnore . includes ( prop ) ) { delete setting [ property ] [ prop ] ; } } } definitionValuesTemp . settings [ settingName ] = formattedSetting ; } } } definitionValues . orgName = orgInfos . records [ 0 ] . Name ; definitionValues . edition = this . mapOrganizationTypeToScratchOrgEdition ( orgInfos . records [ 0 ] . OrganizationType ) ; definitionValues . language = orgInfos . records [ 0 ] . LanguageLocaleKey ; if ( featureList . length > 0 ) { definitionValues . features = featureList ; } definitionValues . settings = definitionValuesTemp . settings ; } ) ; } let filePath = definitionFileName ; if ( this . flags . outputdir ) { filePath = path . join ( this . flags . outputdir , definitionFileName ) ; } const saveToPath = path . join ( process . cwd ( ) , filePath ) ; await fs . writeFile ( saveToPath , this . removeQuotes ( JSON . stringify ( definitionValues , null , 2 ) ) , 'utf8' , function ( err ) { if ( err ) { throw new SfdxError ( `Unable to write definition file at path ${ process . cwd ( ) }: ${ err }` ) ; } } ) ; this . ux . stopSpinner ( 'Done.' ) ; return { org : this . org . getOrgId ( ) , message : definitionValues } ; } private toLowerCamelCase ( label ) { return label . charAt ( 0 ) . toLowerCase ( ) + label . slice ( 1 ) ; } private removeQuotes ( myJson ) { myJson = myJson . replace ( new RegExp ( '"true"' , 'g' ) , true ) ; myJson = myJson . replace ( new RegExp ( '"false"' , 'g' ) , false ) ; return myJson ; } private formatSetting ( myJson ) { this . toLowerCamelCase ( myJson . fullName ) ; delete myJson . fullName ; return myJson ; } param organizationType private mapOrganizationTypeToScratchOrgEdition ( organizationType ) { if ( [ "Team Edition" , "Personal Edition" , "Base Edition" ] . includes ( organizationType ) ) { return "Group" ; } if ( [ "Contact Manager Edition" ] . includes ( organizationType ) ) { return "Professional" ; } if ( [ "Unlimited Edition" ] . includes ( organizationType ) ) { return "Enterprise" ; } const sanitizedOrganizationType = organizationType . replace ( " Edition" , "" ) ; if ( [ "Group" , "Professional" , "Enterprise" , "Developer" ] . includes ( sanitizedOrganizationType ) ) { return sanitizedOrganizationType ; } return "Developer" ; } }