import { flags , SfdxCommand } from '@salesforce/command' ; import { Messages , SfdxError } from '@salesforce/core' ; import { SaveResult } from "jsforce" ; import * as fs from 'fs' ; import * as path from 'path' ; const util = require ( 'util' ) ; Messages . importMessagesDirectory ( __dirname ) ; const defaultLayoutsFolder = 'force-app/main/default/layouts' ; const messages = Messages . loadMessages ( 'texei-sfdx-plugin' , 'source-layouts-cleanorg' ) ; export default class CleanOrg extends SfdxCommand { public static description = messages . getMessage ( 'commandDescription' ) ; public static examples = [ `$ texei:source:layouts:cleanorg` , `$ texei:source:layouts:cleanorg --targetusername myScratchOrg --targetdevhubusername myDevHub` ] ; protected static flagsConfig = { path : flags . string ( { char : 'p' , required : false , description : `path to layouts` } ) } ; protected static requiresUsername = true ; protected static requiresDevhubUsername = true ; protected static requiresProject = true ; public async run ( ) { const orgId15 = ( await this . org . getOrgId ( ) ) . substring ( 0 , 15 ) ; const scratchOrgResult = await this . hubOrg . getConnection ( ) . query ( `Select Id FROM ActiveScratchOrg where ScratchOrg = '${ orgId15 }'` ) as any ; if ( scratchOrgResult . records . length !== 1 ) { throw new SfdxError ( 'This command only works on Scratch Org, you fool!' ) ; } let deletedLayouts = [ ] ; const pathToFile = this . flags . path ? this . flags . path : defaultLayoutsFolder ; const filesPath = path . join ( process . cwd ( ) , pathToFile ) ; const readDir = util . promisify ( fs . readdir ) ; let layoutsFiles = await readDir ( filesPath , "utf8" ) . catch ( err => { if ( err . code === 'ENOENT' ) { const noent = 'No layouts folder found' ; this . ux . log ( noent ) ; deletedLayouts . push ( noent ) ; } else { this . ux . error ( err ) ; } } ) ; if ( layoutsFiles ) { layoutsFiles = layoutsFiles . map ( x => x . replace ( '.layout-meta.xml' , '' ) . replace ( '%26' , '&' ) . replace ( '%27' , '\'' ) . replace ( '%28' , '(' ) . replace ( '%29' , ')' ) . replace ( '%5B' , '[' ) . replace ( '%5D' , ']' ) ) ; let standardObjects = new Set < String > ( layoutsFiles . map ( x => { const obj = x . split ( '-' ) [ 0 ] ; if ( ! obj . includes ( '__' ) ) { return obj ; } } ) ) ; standardObjects . delete ( undefined ) ; const conn = this . org . getConnection ( ) ; const objectList = `'${ Array . from ( standardObjects ) . join ( ) . replace ( /,/gi , '\',\'' ) }'` ; const query = `Select TableEnumOrId, Name from Layout where TableEnumOrId IN (${ objectList }) order by TableEnumOrId` ; const results = await conn . tooling . query ( query ) as any ; let layoutsOnOrg = new Set < String > ( ) ; for ( const layout of results . records ) { layoutsOnOrg . add ( `${ layout . TableEnumOrId }-${ layout . Name }` ) ; } const layoutsToDelete = Array . from ( layoutsOnOrg ) . filter ( lay => layoutsFiles . includes ( lay ) ? undefined : lay ) as string [ ] ; if ( layoutsToDelete . length > 0 ) { this . ux . log ( `Deleting layouts:` ) ; for ( const lay of layoutsToDelete ) { this . ux . log ( lay ) ; deletedLayouts . push ( lay ) ; } let promises = new Array < Promise < SaveResult | SaveResult [ ] > > ( ) ; while ( layoutsToDelete . length ) { promises . push ( conn . metadata . delete ( 'Layout' , layoutsToDelete . splice ( 0 , 10 ) ) ) ; } await Promise . all ( promises ) ; } else { this . ux . log ( `Nothing to delete.` ) ; } } return { deleted : deletedLayouts } ; } }