From 191d9c245df19851025ff0e48e1a06577f33f464 Mon Sep 17 00:00:00 2001 From: Fabien Taillon Date: Thu, 19 Sep 2019 14:12:57 +0200 Subject: [PATCH] Added command to clean standard page layouts --- README.md | 44 ++++++- messages/source-layouts-cleanorg.json | 3 + package.json | 2 +- src/commands/texei/source/layouts/cleanorg.ts | 114 ++++++++++++++++++ 4 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 messages/source-layouts-cleanorg.json create mode 100644 src/commands/texei/source/layouts/cleanorg.ts diff --git a/README.md b/README.md index e457b5c..b10a324 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Link the plugin: sfdx plugins:link . * [`sfdx texei:data:import -d [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-texeidataimport--d-string--u-string---apiversion-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal) * [`sfdx texei:package:dependencies:install [-k ] [-b ] [-p ] [-n ] [-w ] [-r] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-texeipackagedependenciesinstall--k-string--b-string--p-string--n-string--w-number--r--v-string--u-string---apiversion-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal) * [`sfdx texei:source:customlabel:replace -l -v [-p ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-texeisourcecustomlabelreplace--l-string--v-string--p-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal) +* [`sfdx texei:source:layouts:cleanorg [-p ] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-texeisourcelayoutscleanorg--p-string--v-string--u-string---apiversion-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal) * [`sfdx texei:user:update [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-texeiuserupdate--v-string--u-string---apiversion-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal) ## `sfdx texei:data:export -o -d [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` @@ -58,7 +59,7 @@ EXAMPLE Data exported! ``` -_See code: [src/commands/texei/data/export.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.8/src/commands/texei/data/export.ts)_ +_See code: [src/commands/texei/data/export.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/data/export.ts)_ ## `sfdx texei:data:import -d [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` @@ -89,7 +90,7 @@ EXAMPLE Data imported! ``` -_See code: [src/commands/texei/data/import.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.8/src/commands/texei/data/import.ts)_ +_See code: [src/commands/texei/data/import.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/data/import.ts)_ ## `sfdx texei:package:dependencies:install [-k ] [-b ] [-p ] [-n ] [-w ] [-r] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` @@ -142,7 +143,7 @@ EXAMPLE $ texei:package:dependencies:install -u MyScratchOrg -v MyDevHub -k "1:MyPackage1Key 2: 3:MyPackage3Key" -b "DEV" ``` -_See code: [src/commands/texei/package/dependencies/install.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.8/src/commands/texei/package/dependencies/install.ts)_ +_See code: [src/commands/texei/package/dependencies/install.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/package/dependencies/install.ts)_ ## `sfdx texei:source:customlabel:replace -l -v [-p ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` @@ -166,7 +167,40 @@ EXAMPLE $ texei:source:customlabel:replace --label GreatSalesforceBlog --value https://blog.texei.com ``` -_See code: [src/commands/texei/source/customlabel/replace.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.8/src/commands/texei/source/customlabel/replace.ts)_ +_See code: [src/commands/texei/source/customlabel/replace.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/source/customlabel/replace.ts)_ + +## `sfdx texei:source:layouts:cleanorg [-p ] [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` + +delete unused standard layouts from scratch org + +``` +USAGE + $ sfdx texei:source:layouts:cleanorg [-p ] [-v ] [-u ] [--apiversion ] [--json] + [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL] + +OPTIONS + -p, --path=path path to layouts + + -u, --targetusername=targetusername username or alias for the target + org; overrides default target org + + -v, --targetdevhubusername=targetdevhubusername username or alias for the dev hub + org; overrides default dev hub org + + --apiversion=apiversion override the api version used for + api requests made by this command + + --json format output as json + + --loglevel=(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL) [default: warn] logging level for + this command invocation + +EXAMPLES + $ texei:source:layouts:cleanorg + $ texei:source:layouts:cleanorg --targetusername myScratchOrg --targetdevhubusername myDevHub +``` + +_See code: [src/commands/texei/source/layouts/cleanorg.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/source/layouts/cleanorg.ts)_ ## `sfdx texei:user:update [-v ] [-u ] [--apiversion ] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]` @@ -198,5 +232,5 @@ EXAMPLES $ sfdx texei:user:update --values "UserPermissionsKnowledgeUser=true --json" ``` -_See code: [src/commands/texei/user/update.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.8/src/commands/texei/user/update.ts)_ +_See code: [src/commands/texei/user/update.ts](https://github.com/texei/texei-sfdx-plugin/blob/v0.0.9/src/commands/texei/user/update.ts)_ diff --git a/messages/source-layouts-cleanorg.json b/messages/source-layouts-cleanorg.json new file mode 100644 index 0000000..c30c075 --- /dev/null +++ b/messages/source-layouts-cleanorg.json @@ -0,0 +1,3 @@ +{ + "commandDescription": "delete unused standard layouts from scratch org" +} \ No newline at end of file diff --git a/package.json b/package.json index 378ecc1..b477c68 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "texei-sfdx-plugin", "description": "Texeï's plugin for sfdx", - "version": "0.0.8", + "version": "0.0.9", "author": "Texeï", "bugs": "https://github.com/https://github.com/texei/texei-sfdx-plugin/issues", "dependencies": { diff --git a/src/commands/texei/source/layouts/cleanorg.ts b/src/commands/texei/source/layouts/cleanorg.ts new file mode 100644 index 0000000..4ccd3e7 --- /dev/null +++ b/src/commands/texei/source/layouts/cleanorg.ts @@ -0,0 +1,114 @@ +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'); + +// Initialize Messages with the current plugin directory +Messages.importMessagesDirectory(__dirname); + +const defaultLayoutsFolder = 'force-app/main/default/layouts'; + +// 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('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` }) + }; + + // Comment this out if your command does not require an org username + protected static requiresUsername = true; + + // Comment this out if your command does not require a hub org username + protected static requiresDevhubUsername = true; + + // Set this to true if your command requires a project workspace; 'requiresProject' is false by default + protected static requiresProject = true; + + public async run(): Promise { + + // First check this is a Scratch Org, we don't want to delete Layouts from a real org (maybe add a bypass flag later) + // Remove this first part when this.org.checkScratchOrg() works + 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 = []; + + // Read files in directory + const pathToFile = this.flags.path ? this.flags.path : defaultLayoutsFolder; + + const filesPath = path.join( + process.cwd(), + pathToFile + ); + + // Read files + const readDir = util.promisify(fs.readdir); + let layoutsFiles = await readDir(filesPath, "utf8"); + // Don't know why metadata API retrieved & as %26 whereas other characters are ok. Hardcoding for now (booo) + layoutsFiles = layoutsFiles.map(x => x.replace('.layout-meta.xml','').replace('%26','&').replace('%27','\'')); + + // Only look at standard objects + let standardObjects:Set = new Set(layoutsFiles.map(x => { + const obj = x.split('-')[0]; + if (!obj.includes('__')) { + // Should be enough to know if it's a standard object + return obj; + } + })); + standardObjects.delete(undefined); + + // Query the org to get layouts for these standard objects + const conn = this.org.getConnection(); + const objectList:string = `'${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:Set = new Set(); + for (const layout of results.records) { + layoutsOnOrg.add(`${layout.TableEnumOrId}-${layout.Name}`); + } + + const layoutsToDelete:string[] = Array.from(layoutsOnOrg).filter(lay => layoutsFiles.includes(lay) ? undefined : lay) as string[]; + + if (layoutsToDelete.length > 0) { + + // TODO: log after delete, once errors are handled correctly + this.ux.log(`Deleting layouts:`); + for (const lay of layoutsToDelete) { + this.ux.log(lay); + } + + // Use metadata API so that this won't be visible in force:source:status + // This call is limited to 10 records, splitting (maybe refactor later to use destructiveChanges.xml) + let promises: Array> = new Array>(); + + while(layoutsToDelete.length) { + promises.push(conn.metadata.delete('Layout', layoutsToDelete.splice(0,10))); + } + + // TODO: handle errors correctly + await Promise.all(promises); + } + else { + this.ux.log(`Nothing to delete.`); + } + + return { deleted: deletedLayouts }; + } +} \ No newline at end of file