├── src ├── index.ts ├── common │ ├── logger.ts │ ├── JSONUtilities.ts │ ├── rulesInterface.ts │ ├── inventory.ts │ ├── metadataScan.ts │ └── mdmap.ts └── commands │ └── isvte │ ├── listrules.ts │ └── mdscan.ts ├── bin ├── run.cmd └── run ├── test ├── tsconfig.json ├── mocha.opts └── commands │ └── hello │ └── org.test.ts ├── tslint.json ├── .images └── vscodeScreenshot.png ├── CODEOWNERS ├── messages ├── scan.json └── mdscan.json ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── appveyor.yml ├── RELEASENOTES.md ├── README.md ├── .vscode └── launch.json ├── LICENSE.txt ├── .snyk ├── .circleci └── config.yml ├── package.json ├── CODE_OF_CONDUCT.md ├── .eslintrc.json └── EXTENDING.md /src/index.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@salesforce/dev-config/tslint" 3 | } 4 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('@oclif/command').run() 4 | .catch(require('@oclif/errors/handle')) 5 | -------------------------------------------------------------------------------- /.images/vscodeScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/isvte-sfdx-plugin/HEAD/.images/vscodeScreenshot.png -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ts-node/register 2 | --watch-extensions ts 3 | --recursive 4 | --reporter spec 5 | --timeout 5000 6 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /messages/scan.json: -------------------------------------------------------------------------------- 1 | { 2 | "commandDescription" : "Scan a path containing metadata and get technical advice", 3 | "sourceFlagDescription" : "path containing source data in sfdx format" 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-debug.log 2 | *-error.log 3 | /.nyc_output 4 | /dist 5 | /lib 6 | /package-lock.json 7 | /tmp 8 | node_modules 9 | out.json 10 | LabsScan/ 11 | .jekyll-cache/ 12 | _site/ 13 | .DS_Store 14 | result.html 15 | result.json 16 | -------------------------------------------------------------------------------- /messages/mdscan.json: -------------------------------------------------------------------------------- 1 | { 2 | "scanCommandDescription" : "scan a package and provide recommendations based on package inventory", 3 | "sourcefolderFlagDescription" : "directory containing package metadata", 4 | "withloggingFlagDescription" : "enable verbose debug logging", 5 | "showfullinventoryFlagDescription" : "show package inventory only" 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@salesforce/dev-config/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "importHelpers": true, 7 | "sourceMap": true, 8 | "resolveJsonModule": true 9 | }, 10 | "include": [ 11 | "./src/**/*", 12 | "./src/**/**/*", 13 | "./src/**/**/**/*" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "10" 3 | cache: 4 | - '%LOCALAPPDATA%\Yarn -> appveyor.yml' 5 | - node_modules -> yarn.lock 6 | 7 | install: 8 | - ps: Install-Product node $env:nodejs_version x64 9 | - yarn 10 | test_script: 11 | - yarn test 12 | 13 | after_test: 14 | - .\node_modules\.bin\nyc report --reporter text-lcov > coverage.lcov 15 | - ps: | 16 | $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH 17 | Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 18 | bash codecov.sh 19 | 20 | 21 | build: off 22 | 23 | -------------------------------------------------------------------------------- /RELEASENOTES.md: -------------------------------------------------------------------------------- 1 | Starting in April 2022 with Verision 1.15, the ISV Technical Enablement Plugin for SalesforceDX is moving to a scheduled monthly release cycle. Check back here for updates. 2 | 3 | ## May '22 4 | 5 | **Engine Version**: 1.1.16 6 | 7 | **Rules Version**: 20220520 8 | 9 | **Changes**: 10 | 11 | 12 | Support for sfdx projects. `force:source:convert is not longer required` 13 | 14 | Partner Alerts Refresh 15 | 16 | Fix issue with dependency check failing 17 | 18 | Update dependencies 19 | 20 | Fix CustomSettings, CustomMetadata and Labels 21 | 22 | 23 | ## April '22 24 | 25 | **Engine Version**: 1.1.15 26 | 27 | **Rules Version**: 20220418 28 | 29 | **Changes**: 30 | 31 | New Cloud Dependency Check - Commerce, CDP 32 | 33 | Partner Alerts Refresh 34 | -------------------------------------------------------------------------------- /test/commands/hello/org.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@salesforce/command/lib/test'; 2 | import { ensureJsonMap, ensureString } from '@salesforce/ts-types'; 3 | 4 | describe('hello:org', () => { 5 | test 6 | .withOrg({ username: 'test@org.com' }, true) 7 | .withConnectionRequest(request => { 8 | const requestMap = ensureJsonMap(request); 9 | if (ensureString(requestMap.url).match(/Organization/)) { 10 | return Promise.resolve({ records: [ { Name: 'Super Awesome Org', TrialExpirationDate: '2018-03-20T23:24:11.000+0000'}] }); 11 | } 12 | return Promise.resolve({ records: [] }); 13 | }) 14 | .stdout() 15 | .command(['hello:org', '--targetusername', 'test@org.com']) 16 | .it('runs hello:org --targetusername test@org.com', ctx => { 17 | expect(ctx.stdout).to.contain('Hello world! This is org: Super Awesome Org and I will be around until Tue Mar 20 2018!'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | isvte-sfdx-plugin 2 | ============== 3 | 4 | IMPORTANT UPDATE: As of March 11th, 2024, this tool will no longer be actively developed or supported. We will be archiving the repository. We want to thank every one of you. Without, our partners, this tool would not have been a reality. We will continue the development of our products and tools to make integrations with our platform more efficient. Thank you! 5 | 6 | 7 | isvte-sfdx-plugin is a SalesforceDX plugin which will scan the metadata of your package and provide technical enablement advice as well as warnings related to best practices and installations limitations by Salesforce Org Edition. It will also help identify features which contribute to your Trailblazer score for partner program benefits. 8 | 9 | Complete Documentation is available at [https://forcedotcom.github.io/isvte-sfdx-plugin/](https://forcedotcom.github.io/isvte-sfdx-plugin/) 10 | 11 | See [RLEASENOTES.md](RELEASENOTES.md) for information on updates. 12 | 13 | More information on the plugin is available in this [blog post](https://medium.com/inside-the-salesforce-ecosystem/360-view-package-salesforce-isv-technical-enablement-plugin-9adccbd1871d) 14 | 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Attach to Remote", 11 | "address": "127.0.0.1", 12 | "port": 9229, 13 | "localRoot": "${workspaceFolder}" 14 | }, 15 | { 16 | "name": "Unit Tests", 17 | "type": "node", 18 | "request": "launch", 19 | "protocol": "inspector", 20 | "program": "${workspaceRoot}/node_modules/.bin/_mocha", 21 | "args": [ 22 | "--require", "test/helpers/init.js", 23 | "--require", "ts-node/register", 24 | "--require", "source-map-support/register", 25 | "--recursive", 26 | "--reporter", "spec", 27 | "test/**/*.test.ts" 28 | ], 29 | "cwd": "${workspaceRoot}" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Salesforce.com, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.14.1 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | SNYK-JS-LODASH-567746: 7 | - '@salesforce/core > jsforce > lodash': 8 | patched: '2020-04-30T23:17:45.497Z' 9 | - '@salesforce/command > cli-ux > lodash': 10 | patched: '2020-04-30T23:17:45.497Z' 11 | - '@salesforce/command > @oclif/test > fancy-test > lodash': 12 | patched: '2020-04-30T23:17:45.497Z' 13 | - '@salesforce/command > @salesforce/core > jsforce > lodash': 14 | patched: '2020-04-30T23:17:45.497Z' 15 | - sfdx-essentials > @salesforce/command > cli-ux > lodash: 16 | patched: '2020-04-30T23:17:45.497Z' 17 | - sfdx-essentials > @salesforce/command > @oclif/test > fancy-test > lodash: 18 | patched: '2020-04-30T23:17:45.497Z' 19 | - sfdx-essentials > @salesforce/command > @salesforce/core > jsforce > lodash: 20 | patched: '2020-04-30T23:17:45.497Z' 21 | - '@salesforce/core > @salesforce/ts-sinon > sinon > nise > @sinonjs/formatio > @sinonjs/samsam > lodash': 22 | patched: '2020-04-30T23:17:45.497Z' 23 | - '@salesforce/command > @salesforce/core > @salesforce/ts-sinon > sinon > nise > @sinonjs/formatio > @sinonjs/samsam > lodash': 24 | patched: '2020-04-30T23:17:45.497Z' 25 | - sfdx-essentials > @salesforce/command > @salesforce/core > @salesforce/ts-sinon > sinon > nise > @sinonjs/formatio > @sinonjs/samsam > lodash: 26 | patched: '2020-04-30T23:17:45.497Z' 27 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | jobs: 4 | node-latest: &test 5 | docker: 6 | - image: node:latest 7 | working_directory: ~/cli 8 | steps: 9 | - checkout 10 | - restore_cache: &restore_cache 11 | keys: 12 | - v1-npm-{{checksum ".circleci/config.yml"}}-{{checksum "yarn.lock"}} 13 | - v1-npm-{{checksum ".circleci/config.yml"}} 14 | - run: 15 | name: Install dependencies 16 | command: yarn 17 | - run: 18 | name: Testing 19 | command: yarn test 20 | - run: 21 | name: Submitting code coverage to codecov 22 | command: | 23 | ./node_modules/.bin/nyc report --reporter text-lcov > coverage.lcov 24 | curl -s https://codecov.io/bash | bash 25 | node-8: 26 | <<: *test 27 | docker: 28 | - image: node:8 29 | node-10: 30 | <<: *test 31 | docker: 32 | - image: node:10 33 | cache: 34 | <<: *test 35 | steps: 36 | - checkout 37 | - run: 38 | name: Install dependencies 39 | command: yarn 40 | - save_cache: 41 | key: v1-npm-{{checksum ".circleci/config.yml"}}-{{checksum "yarn.lock"}} 42 | paths: 43 | - ~/cli/node_modules 44 | - /usr/local/share/.cache/yarn 45 | - /usr/local/share/.config/yarn 46 | 47 | workflows: 48 | version: 2 49 | "te-scan-plugin": 50 | jobs: 51 | - node-latest 52 | - node-8 53 | - node-10 54 | - cache: 55 | filters: 56 | tags: 57 | only: /^v.*/ 58 | branches: 59 | ignore: /.*/ 60 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { 9 | Logger 10 | } from '@salesforce/core'; 11 | 12 | export class Loggit { 13 | private isvteLogger; 14 | private loggerName; 15 | 16 | public constructor(loggerName = 'isvtePlugin') { 17 | this.loggerName = loggerName; 18 | // this.isvteLogger = Logger.child(this.loggerName); 19 | } 20 | 21 | public async logLine(logMessage : string, type : string = '') { 22 | if (this.isvteLogger == undefined) { 23 | this.isvteLogger = await Logger.child(this.loggerName); 24 | } 25 | switch (type) { 26 | case 'Error': { 27 | this.isvteLogger.error(logMessage + ' -> ' + Loggit.getParent()); 28 | break; 29 | } 30 | case 'Warn': { 31 | this.isvteLogger.warn(logMessage + ' -> ' + Loggit.getParent()); 32 | break; 33 | } 34 | default: { 35 | this.isvteLogger.debug(logMessage + ' -> ' + Loggit.getParent()); 36 | break; 37 | } 38 | } 39 | } 40 | 41 | public async logJSON(logMessage: any, type: string = '') { 42 | this.logLine(JSON.stringify(logMessage),type); 43 | }; 44 | 45 | 46 | static getParent() { 47 | let parents = []; 48 | const stackRegex = /^\s+at\s+(\w+(?:\.\w+)*)\s+\(/gm; 49 | try { 50 | throw new Error(); 51 | } catch (e) { 52 | // matches this function, the caller and the parent 53 | 54 | let match; 55 | while (match = stackRegex.exec(e.stack)) { 56 | // if (match[1] !== 'getParent' && match[1] !== 'Object.logLine') { 57 | parents.push(match[1]); 58 | // } 59 | } 60 | } 61 | return parents.join(':'); 62 | }; 63 | 64 | } 65 | 66 | export async function logLine(namespace : string, logMessage : string, type : string = '') { 67 | const logger = new Loggit(namespace); 68 | logger.logLine(logMessage,type); 69 | } 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isvte-sfdx-plugin", 3 | "description": "Salesforce ISV TE Plugin", 4 | "version": "1.1.16", 5 | "author": "ISV Platform Expert Team", 6 | "bugs": "https://github.com/forcedotcom/isvte-sfdx-plugin/issues", 7 | "dependencies": { 8 | "@oclif/command": "^1.8.16", 9 | "@oclif/config": "^1.18.3", 10 | "@oclif/errors": "^1.3.5", 11 | "@oclif/plugin-help": "^5.1.12", 12 | "@salesforce/command": "^5.1.0", 13 | "@salesforce/core": "^2.37.1", 14 | "alex": "^9.1.0", 15 | "fs-extra": "^10.1.0", 16 | "node-json2html": "^2.1.0", 17 | "sfdx-essentials": "^2.12.0", 18 | "snyk": "^1.931.0", 19 | "tslib": "^2.4.0", 20 | "xml2js": "^0.4.19" 21 | }, 22 | "devDependencies": { 23 | "@oclif/dev-cli": "^1.26.10", 24 | "@oclif/test": "^2.1.0", 25 | "@salesforce/dev-config": "3.0.1", 26 | "@types/chai": "^4.3.1", 27 | "@types/mocha": "^9.1.1", 28 | "@types/node": "^17.0.33", 29 | "chai": "^4.3.6", 30 | "globby": "^11.0.1", 31 | "mocha": "^10.0.0", 32 | "nyc": "^15.1.0", 33 | "ts-node": "^10.7.0", 34 | "tslint": "^6.1.3", 35 | "typescript": "~4.6.4" 36 | }, 37 | "engines": { 38 | "node": ">=8.0.0" 39 | }, 40 | "files": [ 41 | "/lib", 42 | "/messages", 43 | "/npm-shrinkwrap.json", 44 | "/oclif.manifest.json" 45 | ], 46 | "homepage": "https://github.com/forcedotcom/isvte-sfdx-plugin", 47 | "keywords": [ 48 | "sfdx-plugin" 49 | ], 50 | "license": "MIT", 51 | "oclif": { 52 | "commands": "./lib/commands", 53 | "bin": "sfdx", 54 | "topics": { 55 | "mdscan": { 56 | "description": "Scan ." 57 | } 58 | }, 59 | "devPlugins": [ 60 | "@oclif/plugin-help" 61 | ] 62 | }, 63 | "repository": "https://github.com/forcedotcom/isvte-sfdx-plugin", 64 | "scripts": { 65 | "postpack": "rm -f oclif.manifest.json", 66 | "posttest": "tslint -p test -t stylish", 67 | "prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme", 68 | "prepack2": "tsc -b", 69 | "test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"", 70 | "version": "oclif-dev readme && git add README.md", 71 | "snyk-protect": "snyk protect", 72 | "prepare": "yarn run snyk-protect" 73 | }, 74 | "snyk": true 75 | } 76 | -------------------------------------------------------------------------------- /src/common/JSONUtilities.ts: -------------------------------------------------------------------------------- 1 | export function addKeyToObject(destObject: Object, path: string) : any { 2 | return _addKeyToObject(destObject, path.split(/(? 0) { 12 | return _addKeyToObject(destObject[topKey], pathComponents); 13 | } 14 | else { 15 | return destObject[topKey]; 16 | } 17 | } 18 | 19 | export function setValue(destObject: Object, path: string, value: any) : any { 20 | return _setValue(destObject,path.split(/(? 0) { 70 | return _getValue(srcObject[topKey],pathComponents,defaultValue); 71 | } 72 | else { 73 | return srcObject[topKey]; 74 | } 75 | } 76 | else { 77 | return defaultValue; 78 | } 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | * Using welcoming and inclusive language 39 | * Being respectful of differing viewpoints and experiences 40 | * Gracefully accepting constructive criticism 41 | * Focusing on what is best for the community 42 | * Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | * The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | * Personal attacks, insulting/derogatory comments, or trolling 49 | * Public or private harassment 50 | * Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | * Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | * Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/) 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parserOptions": { 4 | "ecmaVersion": 2017 5 | }, 6 | "env": { 7 | "node": true, 8 | "es6": true 9 | }, 10 | "rules": { 11 | "arrow-body-style": ["error", "as-needed"], 12 | "no-alert": 2, 13 | "no-array-constructor": 2, 14 | "no-bitwise": 0, 15 | "no-caller": 0, 16 | "no-catch-shadow": 2, 17 | "no-comma-dangle": 0, 18 | "no-cond-assign": 2, 19 | "no-console": 0, 20 | "no-constant-condition": 2, 21 | "no-control-regex": 2, 22 | "no-debugger": 2, 23 | "no-delete-var": 2, 24 | "no-div-regex": 0, 25 | "no-dupe-keys": 2, 26 | "no-dupe-args": 2, 27 | "no-duplicate-case": 2, 28 | "no-else-return": 0, 29 | "no-empty": 2, 30 | "no-empty-character-class": 2, 31 | "no-eq-null": 0, 32 | "no-eval": 2, 33 | "no-ex-assign": 2, 34 | "no-extend-native": 0, 35 | "no-extra-bind": 2, 36 | "no-extra-boolean-cast": 2, 37 | "no-extra-parens": 0, 38 | "no-extra-semi": 2, 39 | "no-fallthrough": 2, 40 | "no-floating-decimal": 0, 41 | "no-func-assign": 2, 42 | "no-implied-eval": 2, 43 | "no-inline-comments": 0, 44 | "no-inner-declarations": [2, "functions"], 45 | "no-invalid-regexp": 2, 46 | "no-irregular-whitespace": 2, 47 | "no-iterator": 2, 48 | "no-label-var": 2, 49 | "no-labels": 2, 50 | "no-lone-blocks": 2, 51 | "no-lonely-if": 0, 52 | "no-loop-func": 2, 53 | "no-mixed-requires": [0, false], 54 | "no-mixed-spaces-and-tabs": 0, 55 | "no-multi-spaces": 0, 56 | "no-multi-str": 2, 57 | "no-multiple-empty-lines": 0, 58 | "no-native-reassign": 2, 59 | "no-negated-in-lhs": 2, 60 | "no-nested-ternary": 0, 61 | "no-new": 2, 62 | "no-new-func": 0, 63 | "no-new-object": 2, 64 | "no-new-require": 0, 65 | "no-new-wrappers": 2, 66 | "no-obj-calls": 2, 67 | "no-octal": 2, 68 | "no-octal-escape": 2, 69 | "no-param-reassign": 0, 70 | "no-path-concat": 0, 71 | "no-plusplus": 0, 72 | "no-process-env": 0, 73 | "no-process-exit": 2, 74 | "no-proto": 1, 75 | "no-redeclare": 2, 76 | "no-regex-spaces": 2, 77 | "no-reserved-keys": 0, 78 | "no-restricted-modules": 0, 79 | "no-return-assign": 2, 80 | "no-script-url": 2, 81 | "no-self-compare": 0, 82 | "no-sequences": 2, 83 | "no-shadow": 2, 84 | "no-shadow-restricted-names": 2, 85 | "no-space-before-semi": 0, 86 | "no-spaced-func": 2, 87 | "no-sparse-arrays": 2, 88 | "no-sync": 0, 89 | "no-tabs": 0, 90 | "no-ternary": 0, 91 | "no-trailing-spaces": 0, 92 | "no-throw-literal": 0, 93 | "no-undef": 2, 94 | "no-undef-init": 0, 95 | "no-undefined": 0, 96 | "no-underscore-dangle": 0, 97 | "no-unreachable": 2, 98 | "no-unused-expressions": 1, 99 | "no-unused-vars": [ 100 | 2, 101 | { 102 | "vars": "local", 103 | "args": "after-used", 104 | "argsIgnorePattern": "next" 105 | } 106 | ], 107 | "no-use-before-define": 1, 108 | "no-void": 0, 109 | "no-warning-comments": [ 110 | 0, 111 | { 112 | "terms": ["todo", "fixme", "xxx"], 113 | "location": "start" 114 | } 115 | ], 116 | "no-with": 2, 117 | "block-scoped-var": 0, 118 | "brace-style": [0, "1tbs"], 119 | "camelcase": 0, 120 | "comma-dangle": [2, "never"], 121 | "comma-spacing": 0, 122 | "comma-style": 0, 123 | "complexity": [0, 11], 124 | "consistent-return": 2, 125 | "consistent-this": [0, "that"], 126 | "curly": [2, "all"], 127 | "default-case": 0, 128 | "dot-notation": [ 129 | 0, 130 | { 131 | "allowKeywords": true 132 | } 133 | ], 134 | "eol-last": 0, 135 | "eqeqeq": 2, 136 | "func-names": 0, 137 | "func-style": [0, "declaration"], 138 | "generator-star": 0, 139 | "generator-star-spacing": 0, 140 | "global-strict": [0, "never"], 141 | "guard-for-in": 0, 142 | "handle-callback-err": 0, 143 | "indent": 0, 144 | "key-spacing": 0, 145 | "linebreak-style": [0, "windows"], 146 | "max-depth": [0, 4], 147 | "max-len": [0, 80, 4], 148 | "max-nested-callbacks": [0, 2], 149 | "max-params": [0, 3], 150 | "max-statements": [0, 10], 151 | "new-cap": 0, 152 | "new-parens": 2, 153 | "newline-after-var": 0, 154 | "one-var": 0, 155 | "operator-assignment": [0, "always"], 156 | "padded-blocks": 0, 157 | "quote-props": 0, 158 | "quotes": ["error", "single"], 159 | "radix": 0, 160 | "semi": 2, 161 | "semi-spacing": [ 162 | 0, 163 | { 164 | "before": false, 165 | "after": true 166 | } 167 | ], 168 | "sort-vars": 0, 169 | "space-after-function-name": [0, "never"], 170 | "space-after-keywords": [0, "always"], 171 | "space-before-blocks": [0, "always"], 172 | "space-before-function-paren": [0, "always"], 173 | "space-before-function-parentheses": [0, "always"], 174 | "space-in-brackets": [0, "never"], 175 | "space-in-parens": [0, "never"], 176 | "space-infix-ops": 0, 177 | "keyword-spacing": [ 178 | 2, 179 | { 180 | "before": true, 181 | "after": true 182 | } 183 | ], 184 | "space-unary-ops": [ 185 | 2, 186 | { 187 | "words": true, 188 | "nonwords": false 189 | } 190 | ], 191 | "spaced-line-comment": [0, "always"], 192 | "strict": 0, 193 | "use-isnan": 2, 194 | "valid-jsdoc": 0, 195 | "valid-typeof": 2, 196 | "vars-on-top": 0, 197 | "wrap-iife": 0, 198 | "wrap-regex": 0, 199 | "yoda": [2, "never"] 200 | } 201 | } -------------------------------------------------------------------------------- /src/commands/isvte/listrules.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { 9 | SfdxCommand 10 | } from '@salesforce/command'; 11 | import { 12 | mdTypes, 13 | enablementRules, 14 | editionWarningRules, 15 | alertRules, 16 | qualityRules, 17 | rulesVersion, 18 | techAdoptionRules, 19 | dependencyRules, 20 | dataModels 21 | } from '../../common/rules'; 22 | 23 | export default class listrules extends SfdxCommand { 24 | 25 | 26 | public static description = 'display all enablement rules and edition warnings'; 27 | 28 | public static examples = [ 29 | `Display the enablement rules and edition warnings which are checked by the isvte plugin: 30 | \t$sfdx isvte:listrules 31 | Display this help message: 32 | \t$sfdx isvte:listrules -h 33 | 34 | For more information, please connect in the ISV Technical Enablement Plugin 35 | Chatter group on the Salesforce Partner Community https://partners.salesforce.com/0F93A0000004mWj or log an issue in github https://github.com/forcedotcom/isvte-sfdx-plugin 36 | ` 37 | ]; 38 | 39 | public async run(): Promise < any > { // tslint:disable-line:no-any 40 | 41 | 42 | this.ux.log(`Rule Definition version: ${rulesVersion}\n\n`); 43 | 44 | this.ux.styledHeader('Monitored Metadata Types'); 45 | this.ux.table(mdTypes, ['label', 'metadataType']); 46 | this.ux.log('\n\n'); 47 | this.ux.styledHeader('Best Practices and Feature Recommendations:'); 48 | let i=1; 49 | for (var enablementRule of enablementRules) { 50 | if (enablementRule.resultFalse != undefined) { 51 | this.ux.log(`${i++}. ${this.resultToString(enablementRule.resultFalse)}\n`); 52 | } 53 | if (enablementRule.resultTrue != undefined) { 54 | this.ux.log(`${i++}. ${this.resultToString(enablementRule.resultTrue)}\n`); 55 | } 56 | } 57 | this.ux.log('\n'); 58 | i=1; 59 | this.ux.styledHeader('Quality Rules:'); 60 | for (var qualityRule of qualityRules) { 61 | if (qualityRule.resultFalse != undefined) { 62 | this.ux.log(`${i++}. ${this.resultToString(qualityRule.resultFalse)}\n`); 63 | } 64 | if (qualityRule.resultTrue != undefined) { 65 | this.ux.log(`${i++}. ${this.resultToString(qualityRule.resultTrue)}\n`); 66 | } 67 | } 68 | this.ux.log('\n'); 69 | i=1; 70 | this.ux.styledHeader('Partner Alerts:'); 71 | for (var alert of alertRules) { 72 | if (alert.resultFalse != undefined) { 73 | this.ux.log(`${i++}. ${this.resultToString(alert.resultFalse)}\n`); 74 | } 75 | if (alert.resultTrue != undefined) { 76 | this.ux.log(`${i++}. ${this.resultToString(alert.resultTrue)}\n`); 77 | } 78 | } 79 | this.ux.log('\n'); 80 | this.ux.styledHeader('Installation Warnings:'); 81 | this.ux.table(this.getAllEditionWarnings(), ['Edition', 'Item', 'Condition']); 82 | this.ux.log('\n'); 83 | i=1; 84 | this.ux.styledHeader('Dependency Checks:'); 85 | for (var dependencyRule of dependencyRules) { 86 | this.ux.log(`${i++}. ${dependencyRule.label}`); 87 | // this.ux.log(`\t${this.conditionToString(dependencyRule.condition)}\n`); 88 | this.ux.log('\n'); 89 | } 90 | this.ux.log('\n'); 91 | i=1; 92 | this.ux.styledHeader('Data Model Definitions:'); 93 | for (var dataModel of dataModels) { 94 | this.ux.log(`${i++}. ${dataModel.label}:`); 95 | if (dataModel.namespaces) { 96 | this.ux.log(`\tNamespaces: ${dataModel.namespaces.join(', ')}`); 97 | } 98 | if (dataModel.fields) { 99 | this.ux.log(`\tCustom Fields: ${dataModel.fields.join(', ')}`); 100 | } 101 | if (dataModel.objects) { 102 | this.ux.log(`\tStandard Objects: ${dataModel.objects.join(', ')}`); 103 | } 104 | this.ux.log('\n'); 105 | } 106 | this.ux.log('\n'); 107 | this.ux.styledHeader('Tech Adoption'); 108 | this.ux.table(this.getAllAdoptionRules(),['Category','MetadataType','Condition']); 109 | return { 110 | 'rulesVersion' : rulesVersion, 111 | 'monitoredTypes': mdTypes, 112 | 'enablementRules': enablementRules, 113 | 'qualityRules': qualityRules, 114 | 'partnerAlerts': alertRules, 115 | 'editionWarnings': editionWarningRules, 116 | 'dependencyRules': dependencyRules, 117 | 'dataModels': dataModels, 118 | 'techAdoptionRules': techAdoptionRules 119 | }; 120 | 121 | }; 122 | 123 | private resultToString(result) { 124 | let retVal = `${result.label}:\n ${result.message}`; 125 | if (result.url != undefined) { 126 | retVal += `\n ${result.url}` 127 | } 128 | return retVal + '\n'; 129 | } 130 | 131 | 132 | private getAllEditionWarnings() { 133 | let retVal = []; 134 | for (let edition of editionWarningRules) { 135 | retVal.push({ 136 | Edition: edition['name'] 137 | }); 138 | for (let blockingRule of edition['blockingItems']) { 139 | const conditionString = this.conditionToString(blockingRule.condition); 140 | retVal.push({ 141 | Item: blockingRule.label, 142 | Condition: conditionString 143 | }); 144 | } 145 | } 146 | return retVal; 147 | }; 148 | 149 | private getAllAdoptionRules() { 150 | let retVal = []; 151 | for (let category of techAdoptionRules) { 152 | retVal.push({ 153 | Category: category['categoryLabel'] 154 | }); 155 | for (let rule of category['technologies']) { 156 | const conditionString = rule.condition? this.conditionToString(rule.condition) : 'N/A'; 157 | retVal.push({ 158 | MetadataType: rule.name, 159 | Condition: conditionString 160 | }) 161 | } 162 | } 163 | return retVal; 164 | } 165 | 166 | private conditionToString(cond) { 167 | let retVal = ''; 168 | 169 | switch (cond.operator) { 170 | case 'always': 171 | retVal = 'Always'; 172 | break; 173 | case 'never': 174 | retVal = 'Never'; 175 | break; 176 | case 'exists': 177 | retVal = `${cond.metadataType} Exists`; 178 | break; 179 | case 'notexists': 180 | retVal = `${cond.metadataType} Does Not Exist`; 181 | break; 182 | case 'null': 183 | retVal = `${cond.metadataType} is Null`; 184 | break; 185 | case 'gt': 186 | retVal = `${cond.metadataType} is Greater Than ${cond.operand}`; 187 | break; 188 | case 'gte': 189 | retVal = `${cond.metadataType} is Greater Than or Equal to ${cond.operand}`; 190 | break; 191 | case 'lt': 192 | retVal = `${cond.metadataType} is Less Than ${cond.operand}`; 193 | break; 194 | case 'lte': 195 | retVal = `${cond.metadataType} is Less Than or Equal to ${cond.operand}`; 196 | break; 197 | case 'eq': 198 | retVal = `${cond.metadataType} is Equal to ${cond.operand}`; 199 | break; 200 | case 'between': 201 | retVal = `${cond.metadataType} is Between ${cond.operand[0]} And ${cond.operand[1]}`; 202 | break; 203 | } 204 | 205 | if (cond.conditionOr != undefined) { 206 | retVal += ' Or (' + this.conditionToString(cond.conditionOr) + ')'; 207 | } 208 | if (cond.conditionAnd != undefined) { 209 | retVal += ' And (' + this.conditionToString(cond.conditionAnd) + ')'; 210 | } 211 | return retVal; 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /src/common/rulesInterface.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | 9 | /* 10 | Rules explained: 11 | ruleSet = [rule,rule,...] 12 | 13 | rule = { 14 | name: The Rule Name 15 | condition: ruleCondition 16 | resultTrue: result 17 | resultFalse: result 18 | } 19 | 20 | 21 | 22 | result = { 23 | label: Friendly output to display when rule is triggered 24 | message: Text to display 25 | url: link to content 26 | showDetails: boolean 27 | } 28 | A result must have a message and a label 29 | if showDetails is true, then the individual components which pass the condition are included in the result 30 | e.g the first will output just the message. The second will output the message as well as each individual class with and API version that meets the criteria 31 | { 32 | name: 'Metadata API Version', 33 | condition: { 34 | metadataType: 'apiVersions.mdapi', 35 | operator: 'between', 36 | operand: [20,'minAPI'], 37 | }, 38 | resultTrue: { 39 | label: 'Using old Metadata API Version', 40 | message: `You appear to be using a version of Metadata API less than the minimum specified. Use the --minapi flag to adjust the minimum API version.`, 41 | }, 42 | }, 43 | { 44 | name: 'Apex API Version', 45 | condition: { 46 | metadataType: 'apiVersions.ApexClass.*', 47 | operator: 'between', 48 | operand: [20,'minAPI'], 49 | }, 50 | resultTrue: { 51 | label: 'Using old Apex API Version', 52 | message: `You appear to be using an API version less than the minimum specified. Use the --minapi flag to adjust the minimum API version.`, 53 | showDetails: true 54 | } 55 | }, 56 | 57 | If condition resolves to True, then resultTrue is fired. If Condition resolves to false, then resultFalse is fired. 58 | a rule must have a name, a label and a condition. AlertRules, EnablementRules and QualityRules must have a resultTrue and/or a resultFalse 59 | 60 | ruleCondition = { 61 | metadataType: The Metadata Type to query 62 | operator: One of: ['always', 'never', 'exists', 'notexists', 'null', 'gt', 'gte', 'lt', 'lte', 'eq','between'] 63 | operand: value that operator works on. 64 | expiration: dateTime 65 | processAlways: boolean (only within a conditionOr OR a conditionAnd) 66 | conditionPerItem: boolean (only within a conditionAnd) 67 | conditionOr: ruleCondition 68 | conditionAnd: ruleCondition 69 | } 70 | 71 | A ruleCondition must have an operator 72 | If operator is anything other than 'always' or 'never' then ruleCondition must have an operand and a metadataType 73 | If operator is 'between', then operand must be a 2 element array noting the bounds of the between (non inclusive) 74 | ruleCondition cannot have both a conditionAnd AND a conditionOR, but both are optional 75 | 76 | OR: 77 | If conditionOr exists, then the result is an OR of the result of the main condition and the conditionOr condition 78 | If processAlways is true within the conditionOr, then conditionOr will be evaluated even if the main condition is already true 79 | 80 | AND: 81 | If conditionAnd exists then the resuls is an AND of the result of the main condition and the conditionAnd condition 82 | If process Always is true within the conditionAnd, then conditionAnd will be evaluated even if the main condition is already false. 83 | If conditionPerItem is true within the conditionAnd, then the ultimate result is based on the union of items which pass each side of the condition 84 | e.g.: 85 | condition: { 86 | metadataType: 'ApexTrigger.objects.*', 87 | operator: 'gte', 88 | operand: 1, 89 | conditionAnd: { 90 | metadataType: 'Flow.objects.*', 91 | operator: 'gte', 92 | operand: 1, 93 | }, 94 | }, 95 | the above condition will resolve to true if there is any object with an apex trigger and if there is any object with a process builder trigger 96 | 97 | If the condition looks like: 98 | condition: { 99 | metadataType: 'ApexTrigger.objects.*', 100 | operator: 'gte', 101 | operand: 1, 102 | conditionAnd: { 103 | metadataType: 'Flow.objects.*', 104 | operator: 'gte', 105 | operand: 1, 106 | conditionPerItem: true 107 | }, 108 | }, 109 | the condition will resolve to true if any object has both an apex trigger and a process builder trigger. 110 | */ 111 | 112 | /*Interface Definitions */ 113 | 114 | /* Monitored Metadata Types are those which are listed and counted in the output */ 115 | 116 | interface IMetadataType { 117 | label: string, 118 | metadataType: string; 119 | } 120 | 121 | type operatorTypes = 'always' | 'never' | 'exists' | 'notexists' | 'null' | 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'between'; 122 | 123 | interface ICondition { 124 | metadataType: string, //The Metadata Type to query 125 | operator: operatorTypes, // The operator of the condition 126 | operand?: number | [number | 'minAPI',number | 'minAPI'], //value that operator works on 127 | expiration?: string, //Expiration date of the condition 128 | processAlways?: Boolean, //(only within a conditionOr OR a conditionAnd) 129 | conditionPerItem?: Boolean, // (only within a conditionAnd) 130 | conditionOr?: ICondition, //Extra condition to be ORed with this condition 131 | conditionAnd?: ICondition //Extra condition to be ANDed with this condition 132 | showDetails?: Boolean //Toggle whether individual items that meet the condition are displayed 133 | 134 | } 135 | 136 | interface IResult { 137 | label: string, //Friendly output to display when rule is triggered 138 | message: string, //Text block to display 139 | url?: string, //link to content 140 | showDetails?: Boolean //Toggle whether individual items that trigger the rule are displayed 141 | } 142 | 143 | interface IRule { 144 | name: string, // The Rule Name 145 | condition: ICondition, // Logic to determine whether the rule is triggered 146 | resultTrue?: IResult, //Output if the condition is met 147 | resultFalse?: IResult //Output if the condition is not met 148 | } 149 | 150 | interface IInstallRule { 151 | name: string, //Salesforce Edition 152 | blockingItems: {label: String, condition: ICondition}[] //Conditions which, if true, mean the package cannot be installed in this edition 153 | } 154 | 155 | interface ITrailblazerTech { 156 | name: string, //Name for the technology to track 157 | question: string, //Display label for the tech (i.e. the Question that is asked in the Tech Adoption Survey) 158 | points: number, 159 | detectable?: Boolean, 160 | condition?: ICondition, //conditions to count this technology 161 | levelUp?: IResult //How to leverage this tech to score points 162 | } 163 | 164 | interface ITechAdoptionRule { 165 | categoryName: string, // Category for the Tech score rule 166 | categoryLabel: string, //Friendly output for the score rule category 167 | technologies: ITrailblazerTech[] 168 | } 169 | 170 | interface IDependecyRule { 171 | name: string, //Name for the dependency rule 172 | label: string, //Friendly output of the dependency rule 173 | condition: ICondition //Condition which fires the dependency rule 174 | } 175 | 176 | interface IDataModel { 177 | name: string, //Name of the cloud or feature this data model describes 178 | label: string, //Friendly output of the cloud or feature name 179 | fields?: string[], //Array of fields (in Object.Field format) included in this datamodel 180 | objects?: string[], //Array of objects included in this data model 181 | namespaces?: string[], //Array of namespaces included in this data model 182 | metadataTypes?: string[], //Array of additional metadata components 183 | } 184 | -------------------------------------------------------------------------------- /EXTENDING.md: -------------------------------------------------------------------------------- 1 | ## Extending the Plugin 2 | 3 | ### **Add or Customize Rules** 4 | 5 | The isvte plugin reports are controlled by a set of rules which are executed against an inventory of the package. These rules are defined in the file [rules.ts](src/common/rules.ts). The rules are categorized based on the sections of the report they apply to. 6 | 7 | To see the inventory structure that the rules run against, execute the isvte plugin with the --json flag and inspect the result.MetadataInventory object. 8 | 9 | ``` 10 | sfdx isvte:mdscan -d --json | jq .result.MetadataInventory 11 | ``` 12 | 13 | The output will look similar to: 14 | 15 | ``` 16 | { 17 | "CustomApplication": { 18 | "index": "0", 19 | "count": 1, 20 | "LightingAppCount": 1, 21 | "LightningConsoleCount": 0, 22 | "ClassicAppCount": 0, 23 | "ClassicConsoleCount": 0 24 | }, 25 | "ApexClass": { 26 | "index": "1", 27 | "count": 2, 28 | "FutureCalls": 0, 29 | "AuraEnabledCalls": 1, 30 | "InvocableCalls": 0, 31 | "BatchApex": 0, 32 | "SchedulableApex": 0, 33 | "ApexRest": 0, 34 | "ApexSoap": 0 35 | }, 36 | "FlexiPage": { 37 | "index": "2", 38 | "count": 2 39 | }, 40 | "LightningComponentBundle": { 41 | "index": "3", 42 | "count": 5, 43 | "ExposedComponents": 3, 44 | "targets": { 45 | "lightning__AppPage": 3, 46 | "lightning__HomePage": 1 47 | } 48 | }, 49 | "PermissionSet": { 50 | "index": "4", 51 | "count": 1 52 | }, 53 | "CustomTab": { 54 | "index": "5", 55 | "count": 1 56 | }, 57 | "apiVersions": { 58 | "mdapi": 46, 59 | "ApexClass": { 60 | "appAnalyticsRequestController": 45, 61 | "appAnalyticsRequestControllerTest": 45 62 | }, 63 | "LightningComponentBundle": { 64 | "appAnalyticsRequestCard": 45, 65 | "appAnalyticsRequestList": 45, 66 | "appAnalyticsRequestPageWrapper": 45, 67 | "newAnalyticsRequestForm": 45, 68 | "pubsub": 45 69 | } 70 | }, 71 | "componentProperties": {}, 72 | "dependencies": { 73 | "namespaces": { 74 | "sfLMA": 1, 75 | "sfLma": 1 76 | }, 77 | "components": { 78 | "sfLMA__Package__c": { 79 | "type": "Custom", 80 | "name": "Package", 81 | "extension": "c", 82 | "namespace": "sfLMA", 83 | "fullName": "sfLMA__Package__c" 84 | }, 85 | } 86 | } 87 | } 88 | 89 | ``` 90 | 91 | #### Querying The Inventory 92 | 93 | Inventory items are queried using the ```metadataType``` propery within rules. This metadataType is almost exactly a metadataType as defined in the [Metadata API Documentation](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_types_list.htm) but also includes other items that come out of the plugin's inventory of the package. 94 | To reference Lightning Web Components exposed to App Pages from the inventory above you can do: 95 | ``` 96 | metadataType: 'LightningComponentBundle.targets.lightning__AppPage' 97 | ``` 98 | 99 | You can also use * as a wildcard, so to identify the api versions of all ApexClasses, you can do: 100 | ``` 101 | metadataType: 'apiVersions.ApexClass.*' 102 | ``` 103 | 104 | #### Rule Conditions 105 | Most rules have a condition which causes them to fire. This condition is defined as: 106 | 107 | ``` 108 | interface ICondition { 109 | metadataType: string, //The Metadata Type to query 110 | operator: operatorTypes, // The operator of the condition 111 | operand?: number | [number | 'minAPI',number | 'minAPI'], //value that operator works on 112 | expiration?: string, //Expiration date of the condition 113 | processAlways?: Boolean, //(only within a conditionOr OR a conditionAnd) 114 | conditionPerItem?: Boolean, // (only within a conditionAnd) 115 | conditionOr?: ICondition, //Extra condition to be ORed with this condition 116 | conditionAnd?: ICondition //Extra condition to be ANDed with this condition 117 | showDetails?: Boolean //Toggle whether individual items that meet the condition are displayed 118 | } 119 | ``` 120 | 121 | ```metadataType``` is defined as above. 122 | 123 | ```operator``` can be one of: 'always' | 'never' | 'exists' | 'notexists' | 'null' | 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'between' 124 | 125 | ```operand``` is the numerical value (or the placeholder minAPI) that the operator works against. 126 | 127 | So a condition like: 128 | ``` 129 | { 130 | metadataType: 'dependencies.namespaces.sfLMA', 131 | operator: 'eq', 132 | operand: 1 133 | } 134 | ``` 135 | would return true in the sample inventory above. 136 | 137 | 138 | ```conditionAnd``` and ```conditionOr``` are extra conditions that can be used to increase the complexity of a query. 139 | 140 | The condition: 141 | 142 | ``` 143 | { 144 | metadataType: 'Flow.FlowTemplate', 145 | operator: 'notexists', 146 | conditionAnd: { 147 | processAlways: false, 148 | metadataType: 'Flow', 149 | operator: 'exists' 150 | } 151 | }, 152 | ``` 153 | would return true if there are no Flow Templates, but there are Flows. 154 | 155 | A condition can have a ```conditionAnd``` or a ```conditionOr``` or neither, but not both. You can however, chain as many conditions as you need using ```conditionAnd``` and ```conditionOr```. 156 | 157 | ``` 158 | metadataType: 'apiVersions.ApexClass.*', 159 | operator: 'notexists', 160 | conditionOr: { 161 | metadataType: 'componentProperties.ApexClass.*.StripInaccessible', 162 | operator: 'eq', 163 | operand: 1, 164 | conditionOr: { 165 | metadataType: 'componentProperties.ApexClass.*.SECURITY_ENFORCED', 166 | operator: 'eq', 167 | operand: 1, 168 | conditionAnd: { 169 | metadataType: 'apiVersions.ApexClass.*', 170 | operator: 'gte', 171 | operand: 48, 172 | conditionPerItem: true 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | 179 | 180 | #### Rule Results 181 | 182 | Several Rule Types have a "Result" component which describes what is presented based on the outcome of the condition. 183 | 184 | A Result is defined as: 185 | ``` 186 | interface IResult { 187 | label: string, //Friendly output to display when rule is triggered 188 | message: string, //Text block to display 189 | url?: string, //link to content 190 | showDetails?: Boolean //Toggle whether individual items that trigger the rule are displayed 191 | } 192 | ``` 193 | 194 | #### Inventory Report Rules 195 | 196 | Metadata components listed in the Inventory report are defined in the ```mdTypes``` list within the rules file. Note that this list defines only what is displayed in the report; it has no impact on what is actually inventoried. 197 | 198 | Inventoried items conform to the following interface: 199 | ``` 200 | interface IMetadataType { 201 | label: string, 202 | metadataType: string; 203 | } 204 | ``` 205 | ```label``` is the text to be displayed. 206 | 207 | ```metadataType``` is the item to be counted. 208 | 209 | 210 | #### Enablement, Quality, and Partner Alert Rules 211 | Enablement content listed under the **Best Practices and Feature Recommendations** heading is defined in the ```enablementRules``` list. 212 | 213 | Code Quality content listed under the **Quality Rules** heading is defined in the ```qualityRules``` list. 214 | 215 | Partner Alerts listed under the **Partner Alerts** heading are defined in the ```alertRules``` list. 216 | 217 | These rules all follow the general structure: 218 | ``` 219 | interface IRule { 220 | name: string, // The Rule Name 221 | condition: ICondition, // Logic to determine whether the rule is triggered 222 | resultTrue?: IResult, //Output if the condition is met 223 | resultFalse?: IResult //Output if the condition is not met 224 | } 225 | ``` 226 | 227 | #### Edition Installation Rules 228 | The rules that define which editions your package can be installed into are defined in the ```editionWarningRules``` list. They are defined as: 229 | ``` 230 | interface IInstallRule { 231 | name: string, //Salesforce Edition 232 | blockingItems: {label: String, condition: ICondition}[] //Conditions which, if true, mean the package cannot be installed in this edition 233 | } 234 | ``` 235 | 236 | #### Dependency Rules 237 | Dependency Rules identify a dependency on a feature, cloud, or other item. They are defined in the ```dependencyRules``` list and follow the structure: 238 | ``` 239 | interface IDependecyRule { 240 | name: string, //Name for the dependency rule 241 | label: string, //Friendly output of the dependency rule 242 | condition: ICondition //Condition which fires the dependency rule 243 | } 244 | ``` 245 | 246 | #### Data Models 247 | Data models can be used to dynamically create a dependency rule. They are defined in the object ```dataModels``` and follow the structure: 248 | ``` 249 | interface IDataModel { 250 | name: string, //Name of the cloud or feature this data model describes 251 | label: string, //Friendly output of the cloud or feature name 252 | fields?: string[], //Array of fields (in Object.Field format) included in this datamodel 253 | objects?: string[], //Array of objects included in this data model 254 | namespaces?: string[], //Array of namespaces included in this data model 255 | } 256 | ``` 257 | 258 | This rule type will fire if any references are found to the namespaces within the ```namespaces``` property or if any references are found to objects listed in the ```objects``` property. 259 | 260 | For example, Work.com uses the wkcc namespace for the Command Center managed package and enabling Work.com in an org creates the objects: Employee,EmployeeCrisisAssessment, InternalOrganizationUnit, and Crisis. If a package refers to these items, then there is a dependecy on Work.com. 261 | 262 | ``` 263 | { 264 | name: 'work.com', 265 | label: 'Work.com', 266 | namespaces: ['wkcc'], 267 | objects: ['Employee','EmployeeCrisisAssessment','InternalOrganizationUnit','Crisis'], 268 | fields: ['Location.Status__c'] 269 | }, 270 | ``` 271 | 272 | -------------------------------------------------------------------------------- /src/commands/isvte/mdscan.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | 8 | import { 9 | flags, 10 | SfdxCommand 11 | } from '@salesforce/command'; 12 | import { 13 | SfdxError, 14 | } from '@salesforce/core'; 15 | 16 | import { 17 | Loggit 18 | } from '../../common/logger'; 19 | import fs = require('fs-extra'); 20 | //import json2html = require('node-json2html'); 21 | 22 | import { 23 | packageInventory 24 | } from '../../common/inventory'; 25 | import { 26 | minAPI 27 | } from '../../common/rules'; 28 | 29 | import { 30 | inventoryPackage, 31 | parseXML 32 | } from '../../common/metadataScan' 33 | 34 | import * 35 | as util 36 | from 'util' 37 | 38 | import MetadataFilterFromPackageXml 39 | from 'sfdx-essentials/lib/commands/essentials/metadata/filter-from-packagexml'; 40 | 41 | 42 | export default class mdscan extends SfdxCommand { 43 | 44 | private showFullInventory = false; 45 | private formatHTML = false; 46 | private languageScan = false; 47 | private showTechAdoption = false; 48 | private sourceFolder = ''; 49 | private sfdxPackageXml: string; 50 | private suppressZeroInv = false; 51 | private suppressAllInv = false; 52 | private suppressEnablement = false; 53 | private suppressAlerts = false; 54 | private suppressWarnings = false; 55 | private suppressQuality = false; 56 | private suppressAPI = false; 57 | // private suppressAdoptionScore = false; 58 | private loggit; 59 | private packageInventory; 60 | private tmpParentFolder = '.isvtetmp_' + Math.random().toString(36).substring(2, 15); 61 | private sfdxConvertFolder = this.tmpParentFolder + '/mdapi' ; 62 | private sfdxConvertFolderFilter = this.tmpParentFolder + '/mdapiFiltered'; 63 | 64 | public static description = 'scan a package and provide recommendations based on package inventory'; 65 | 66 | public static examples = [ 67 | `Scan a package and provide inventory of monitored metadata items and enablement messages: 68 | \t$sfdx isvte:mdscan -d ./mdapi 69 | 70 | Scan a package using a SFDX project and a package.xml file: 71 | \t$sfdx isvte:mdscan -d ./force-app/main/default -p ./config/package.xml 72 | 73 | Scan a package and provide a complete inventory of package metadata: 74 | \t$sfdx isvte:mdscan -d ./mdapi -y 75 | 76 | Do not display alerts and warnings: 77 | \t$sfdx isvte:mdscan -d ./mdapi -s alerts,warnings 78 | 79 | Display this help message: 80 | \t$sfdx isvte:mdscan -h 81 | 82 | For more information, please connect in the ISV Technical Enablement Plugin 83 | Chatter group on the Salesforce Partner Community https://partners.salesforce.com/0F93A0000004mWj or log an issue in github https://github.com/forcedotcom/isvte-sfdx-plugin 84 | ` 85 | ]; 86 | 87 | 88 | protected static flagsConfig = { 89 | sourcefolder: flags.directory({ 90 | char: 'd', 91 | description: 'directory containing package metadata', 92 | default: 'mdapiout' 93 | }), 94 | sfdxpackagexml: flags.string({ 95 | char: 'p', 96 | description: 'path to a package.xml file if current folder is a SFDX Project' 97 | }), 98 | showfullinventory: flags.boolean({ 99 | char: 'y', 100 | description: 'show package inventory only' 101 | }), 102 | suppress: flags.array({ 103 | char: 's', 104 | description: `comma separated list of items to suppress.\n Valid options are: ZeroInventory, Inventory, Enablement, Quality, Alerts, Warnings, API` 105 | }), 106 | techadoption: flags.boolean({ 107 | char: 't', 108 | description: `Show Tech Adoption calculation for Trailblazer scoring` 109 | }), 110 | minapi: flags.integer({ 111 | description: 'minimum api version to use during quality checks', 112 | default: minAPI 113 | }), 114 | languagescan: flags.boolean({ 115 | char: 'l', 116 | description: 'perform a scan for potentially exclusive or offensive language' 117 | }), 118 | html: flags.boolean({ 119 | description: 'generate html formatted output' 120 | }), 121 | 122 | }; 123 | 124 | 125 | public async run(): Promise { // tslint:disable-line:no-any 126 | 127 | this.loggit = new Loggit('isvtePlugin'); 128 | 129 | this.showFullInventory = this.flags.showfullinventory; 130 | this.sourceFolder = this.flags.sourcefolder; 131 | this.showTechAdoption = this.flags.techadoption; 132 | this.sfdxPackageXml = this.flags.sfdxpackagexml; 133 | this.languageScan = this.flags.languagescan; 134 | this.formatHTML = this.flags.html; 135 | 136 | //Check Suppress Flags 137 | if (this.flags.suppress) { 138 | this.flags.suppress.forEach(element => { 139 | this.suppressZeroInv = this.suppressZeroInv || element.toLowerCase() == 'zeroinventory'; 140 | this.suppressAllInv = this.suppressAllInv || element.toLowerCase() == 'inventory'; 141 | this.suppressEnablement = this.suppressEnablement || element.toLowerCase() == 'enablement'; 142 | this.suppressAlerts = this.suppressAlerts || element.toLowerCase() == 'alerts'; 143 | this.suppressWarnings = this.suppressWarnings || element.toLowerCase() == 'warnings'; 144 | this.suppressQuality = this.suppressQuality || element.toLowerCase() == 'quality'; 145 | this.suppressAPI = this.suppressAPI || element.toLowerCase() == 'api'; 146 | // this.suppressAdoptionScore = this.suppressAdoptionScore || element.toLowerCase() == 'readoption' 147 | }); 148 | } 149 | 150 | if (!fs.existsSync(this.sourceFolder)) { 151 | this.loggit.logLine(`your source folder ${this.sourceFolder} doesn't exist`, 'Error'); 152 | throw new SfdxError(`Source Folder ${this.sourceFolder} does not exist`, 'SourceNotExistError'); 153 | } 154 | 155 | 156 | // If this is source formatted, convert it to MDAPI Format 157 | 158 | if (fs.existsSync(this.sourceFolder + '/sfdx-project.json')) { 159 | const exec = util.promisify(require('child_process').exec); 160 | 161 | try { 162 | 163 | // force:source:convert in a temporary folder 164 | const sfdxConvertCommand = `sfdx force:source:convert -d ${this.sfdxConvertFolder} -r ${(this.sourceFolder != mdscan.flagsConfig.sourcefolder.default) ? this.sourceFolder : './'}`; 165 | const { stderr } = await exec(sfdxConvertCommand); 166 | if (stderr) { 167 | throw new SfdxError(`Unable to convert ${this.sourceFolder} to MDAPI Format`, 'ConversionToMetadataError'); 168 | } 169 | 170 | // If argument packageXml is sent, convert SFDX project into metadatas folder then filter it 171 | if (this.sfdxPackageXml) { 172 | // Filter metadatas folder using package.xml 173 | await MetadataFilterFromPackageXml.run([ 174 | '-i', this.sfdxConvertFolder, 175 | '-o', this.sfdxConvertFolderFilter, 176 | '-p', this.sfdxPackageXml, 177 | '-s']); 178 | this.sourceFolder = this.sfdxConvertFolderFilter; // Set filtered mdapi folder as sourceFolder 179 | // Remove filtered metadata folder if existing 180 | if (fs.existsSync(this.sfdxConvertFolder)) { 181 | fs.removeSync(this.sfdxConvertFolder); 182 | } 183 | } 184 | else { 185 | this.sourceFolder = this.sfdxConvertFolder; 186 | } 187 | } 188 | catch (e) { 189 | throw e; 190 | } 191 | 192 | } 193 | 194 | // Process MD Scan 195 | 196 | const packagexml = `${this.sourceFolder}/package.xml`; 197 | 198 | let packageJSON:object = parseXML(packagexml, true); 199 | if (packageJSON['Package']) { 200 | packageJSON = packageJSON['Package']; 201 | } else { 202 | this.loggit.logLine(`Package.xml ${packagexml} appears to be invalid `, 'Error'); 203 | throw new SfdxError(`Package.xml ${packagexml} appears to be invalid `, 'InvalidPackageXMLError'); 204 | 205 | } 206 | 207 | this.cleanInventory(packageJSON); 208 | this.packageInventory = new packageInventory(); 209 | 210 | if (this.flags.minapi) { 211 | this.packageInventory.setMinAPI(this.flags.minapi); 212 | } 213 | this.packageInventory.setMetadata(inventoryPackage(this.sourceFolder, packageJSON, {scanLanguage: this.languageScan})); 214 | // Generate Report 215 | 216 | //Just show inventory 217 | if (this.showFullInventory) { 218 | this.ux.styledHeader('Inventory of Package:'); 219 | this.ux.table(this.packageInventory.getFullInvArray(), ['metadataType', 'count']); 220 | } 221 | //HTML Format 222 | 223 | else if (this.formatHTML) { 224 | this.ux.log('Comming Soon'); 225 | } 226 | /* 227 | Header 228 | Title 229 | Date 230 | 231 | Monitored Inventory 232 | [Items] 233 | 234 | Enablement 235 | [Items] 236 | [Details] 237 | */ 238 | /* 239 | json2html.component.add({ 240 | "pageHeader": {"<>":"div","id":"${id}","html":[ 241 | {"<>":"h2","html":"ISVTE Plugin Scan Results"}, 242 | {"<>":"div","html":[ 243 | {"<>":"div","html":"Date:"}, 244 | {"<>":"div","html":"${Date}"} 245 | ]}, 246 | {"<>":"div","html":"Rules Version:"}, 247 | {"<>":"div","html":"${RulesVersion}"} 248 | ]}, 249 | "sectionHeader" : {"<>":"h2","html":"${name}"}, 250 | "enablementLink":{"<>":"a","href":"${url}","text":"${url}"}, 251 | "enablementComponents":{"<>":"p","html":"Impacted Components: ${value}"}, 252 | "enablementContent":{"<>":"li","html":[ 253 | {"<>":"p","html":"${label}"}, 254 | {"<>":"p","html":"${message}"}, 255 | {"[]":"enablementComponents","obj":function(){ 256 | if (this.exceptions != undefined && this.exceptions.length > 0) 257 | return(this.exceptions.join(', ')); 258 | else 259 | return null; 260 | }}, 261 | {"[]":"enablementLink"}, 262 | ]}, 263 | "inventoryItem" :{"<>":"tr","html":[ 264 | {"<>":"td","html":"
${label}
"},
265 |               {"<>":"td","html":"${count}"}
266 |             ]},
267 |             "installationBlockingItems":{"<>":"li","text":"${label}"},
268 |             "installationWarnings":{"<>":"li","html":[
269 |               {"<>":"p","html":"Package Cannot be installed in ${edition} due to:"},
270 |               {"<>":"ul","html":[
271 |                 {"[]":"installationBlockingItems","obj":function(){
272 |                   return(this.blockingItems);
273 |                 }
274 |                 }
275 |               ]}
276 |             ]},
277 |             "dependencies": {"<>":"li","html":[
278 |               {"html":"${label}"},
279 |               {"obj":function(){
280 |                 if (this.items != undefined && this.items.length > 0)
281 |                   return(this.items.join(', '))
282 |                 else
283 |                   return null
284 |               },
285 |               "html":"${value}"}
286 |             ]}
287 |           })
288 |       const templates= {
289 |         "pageTemplate": [
290 |           {"[]":"pageHeader","obj":function(){
291 |             return(this.Status);
292 |         }},
293 |         {"[]":"sectionHeader","obj":function(){
294 |             return({"name":"Metadata Inventory"});
295 |         }},
296 |         {"<>":"table","html":[
297 |           {"<>":"thead","html":[
298 |             {"<>":"tr","html":[
299 |               {"<>":"th","html":"Metadata Type"},
300 |               {"<>":"th","html":"Count"},
301 |             ]}
302 |           ]},
303 |           {"<>":"tbody","html":[
304 |             {"[]":"inventoryItem","obj":function(){
305 |               return(this.MonitoredItems);
306 |             }}
307 |           ]}
308 |          
309 |         ]},
310 |         {"[]":"sectionHeader","obj":function(){
311 |           return({"name":"Best Practices and Feature Recommendations"});
312 |         }},
313 |         {"<>":"ul","html":[
314 |           {"[]":"enablementContent","obj":function(){
315 |             return(this.Recommendations);
316 |           }}
317 |         ]},
318 |         {"[]":"sectionHeader","obj":function(){
319 |           return({"name":"Quality Rules"});
320 |         }},
321 |         {"<>":"ul","html":[
322 |           {"[]":"enablementContent","obj":function(){
323 |             return(this.CodeQualityNotes);
324 |           }}
325 |         ]},
326 |         {"[]":"sectionHeader","obj":function(){
327 |           return({"name":"Partner Alerts"});
328 |         }},
329 |         {"<>":"ul","html":[
330 |           {"[]":"enablementContent","obj":function(){
331 |             return(this.Alerts);
332 |           }}
333 |         ]},
334 |         {"[]":"sectionHeader","obj":function(){
335 |           return({"name":"Installation Warnings"});
336 |         }},
337 |         {"<>":"ul","html":[
338 |           {"<>":"h3","html":"Editions"},
339 |           {"[]":"installationWarnings","obj":function(){
340 |             return(this.InstallationWarnings);
341 |           }},
342 |           {"<>":"h3","html":"Dependencies"},
343 |           {"[]":"dependencies","obj":function(){
344 |             return(this.Dependencies);
345 |           }}
346 |         ]},
347 | 
348 |         ]
349 |       }
350 | 
351 |       htmlOut += json2html.render(this.packageInventory.getJSONOutput(),templates.pageTemplate);
352 |       
353 |       fs.writeFileSync('result.html',htmlOut);
354 |       cli.open('result.html');
355 | 
356 |     }*/
357 |     //Text format
358 |     else {
359 |       if (!this.suppressAllInv) {
360 |         this.ux.styledHeader('Inventory of Package:');
361 |         let inventoryArray = this.packageInventory.getMonitoredInvArray().filter(element => {
362 |           return (!this.suppressZeroInv || element.count > 0);
363 |         });
364 | 
365 |         this.ux.table(inventoryArray,{
366 |           label: {header:"Metadata Type"},
367 |           count: {header:"Count"}
368 |         });
369 |         this.ux.log('\n');
370 |         
371 |       }
372 |       if (!this.suppressEnablement) {
373 |         let recommendations = this.packageInventory.getEnablementMessages();
374 |         if (recommendations.length > 0) {
375 |           this.ux.styledHeader('Best Practices and Feature Recommendations:');
376 |           for (var recommendation of recommendations) {
377 |             let message = `${recommendation.label}:\n${recommendation.message}\n`;
378 |             if (recommendation.url != undefined) {
379 |               message += `URL:${recommendation.url}\n`;
380 |             }
381 |             if (recommendation.exceptions != undefined && recommendation.exceptions.length > 0) {
382 |               message += `Components: ${recommendation.exceptions.join(', ')}\n`;
383 |             }
384 |             this.ux.log(message);
385 |           }
386 |         }
387 | 
388 |       }
389 | 
390 |       if (!this.suppressQuality) {
391 |         //TODO: suppress API is not used
392 |         let recommendations = this.packageInventory.getQualityRecommendations();
393 |         if (recommendations.length > 0) {
394 |           this.ux.styledHeader('Quality Rules:');
395 |           for (var recommendation of recommendations) {
396 |             let message = `${recommendation.label}:\n${recommendation.message}\n`;
397 |             if (recommendation.url != undefined) {
398 |               message += `URL:${recommendation.url}\n`;
399 |             }
400 |             if (recommendation.exceptions != undefined && recommendation.exceptions.length > 0) {
401 |               message += `Components: ${recommendation.exceptions.join(', ')}\n`;
402 |             }
403 |             this.ux.log(message);
404 |           }
405 |         }
406 | 
407 |       }
408 |       if (!this.suppressAlerts) {
409 |         let recommendations = this.packageInventory.getAlerts();
410 |         if (recommendations.length > 0) {
411 |           this.ux.styledHeader('Partner Alerts:');
412 |           for (var recommendation of recommendations) {
413 |             let message = `${recommendation.label}:\n${recommendation.message}\n`;
414 |             if (recommendation.url != undefined) {
415 |               message += `URL:${recommendation.url}\n`;
416 |             }
417 |             if (recommendation.exceptions != undefined && recommendation.exceptions.length > 0) {
418 |               message += `Components: ${recommendation.exceptions.join(', ')}\n`;
419 |             }
420 |             this.ux.log(message);
421 |           }
422 |         }
423 | 
424 |       }
425 |       if (!this.suppressWarnings) {
426 |         this.ux.styledHeader('Installation Warnings');
427 |         let warnings = this.packageInventory.getInstallationWarnings();
428 |         if (warnings.length > 0) {
429 |           for (var warning of warnings) {
430 |             this.ux.log(`Package cannot be installed in ${warning['edition']} due to:`)
431 |             for (var blockingItem of warning['blockingItems']) {
432 |               this.ux.log(`  ${blockingItem['label']} `);
433 |             }
434 |             this.ux.log('\n');
435 |           }
436 |         } else {
437 |           this.ux.log('Can be installed in any Edition\n');
438 |         }
439 | 
440 |         let dependencies = this.packageInventory.getDependencies();
441 |         if (dependencies.length > 0) {
442 |           this.ux.styledHeader('Dependencies:');
443 |           for (var dependency of dependencies) {
444 |             this.ux.log(`${dependency.label}`);
445 |             if (dependency['items']) {
446 |               for (var depItem of dependency.items) {
447 |                 this.ux.log(`\t${depItem}`);
448 |               }
449 |             }
450 |           
451 |           }
452 |           this.ux.log('\n');
453 |         }
454 |       }
455 | 
456 |       if (this.showTechAdoption) {
457 |         this.ux.styledHeader('Technology Adoption:');
458 |         this.ux.log('The responses below correspond to the ISV Technology Adoption Survey for FY\'22.\nPlease note that the points and answers are not indended to be exhaustive and only reflect that which can be identified within the metadata source.\nAccess the Survey here: https://www.getfeedback.com/r/2ssZhMKB/\n\n');
459 |         for (var category of this.packageInventory.getTechAdoptionScore()) {
460 |           this.ux.log(`${category.categoryLabel} (${category.points} Points)\n`);
461 |           let i = 1;
462 |           for (var tech of category.technologies) {
463 |             this.ux.log(` ${i++}. ${tech.name}: ${tech.question} (${tech.maxPoints} Points) \n   ${tech.found ? 'Found' : tech.detectable ? 'Not Found': 'Not Identifiable in Metadata'} `);
464 |             if (tech.levelUp != undefined) {
465 |               this.ux.log(`\t${tech.levelUp.message}`);
466 |               if (tech.levelUp.url != undefined) {
467 |                 this.ux.log(`\tURL:${tech.levelUp.url}`);
468 |               }
469 |             }
470 |             this.ux.log('\n');
471 |           }
472 |           this.ux.log('\n');
473 |         }
474 |       
475 |       }
476 | 
477 |       if (this.languageScan) {
478 |         this.ux.styledHeader('Language Warnings');
479 |         this.ux.log('Language scan is provided by https://github.com/get-alex/');
480 |         if (Object.entries(this.packageInventory.getLanguageWarnings()).length == 0) {
481 |           this.ux.log('No issues found');
482 |         }
483 |         for (let [mdTypeKey, mdTypeValue] of Object.entries(this.packageInventory.getLanguageWarnings())) {
484 |           this.ux.log(`Metadata Type: ${mdTypeKey}`);
485 |           for (let [mdItemKey, mdItemValue] of Object.entries(mdTypeValue)) {
486 |             this.ux.log(`\t${mdItemKey}`);
487 |             for (let result of mdItemValue) {
488 |               this.ux.log(`\t\tSource: ${result.context}`);
489 |               this.ux.log(`\t\tException: ${result.message}`);
490 |               this.ux.log(`\t\tLine: ${result.line}`);
491 |               if(result.details) {
492 |                 this.ux.log(`\t\tDetails: ${result.details}`);
493 |               }
494 |               this.ux.log('\n');
495 |             }
496 |           }
497 |         }
498 |       }
499 | 
500 |     }
501 | 
502 |     //Feedback
503 |     this.ux.log('Please provide feedback on this tool: https://bit.ly/TEPluginFeedback');
504 | 
505 |     // Get MdScan JSON Output
506 |     const outputRes = this.packageInventory.getJSONOutput();
507 | 
508 |     // Remove temp folders if existing
509 |     if (fs.existsSync(this.tmpParentFolder)) {
510 |       fs.removeSync(this.tmpParentFolder);
511 | 
512 |     }
513 | 
514 |     return outputRes;
515 | 
516 |   }
517 | 
518 |   cleanInventory(packageJSON: object) {
519 |     //normalize the inventory so that there is only 1 entry for each metadataType
520 |    
521 |     if (packageJSON['types'] && Array.isArray(packageJSON['types'])) {
522 |       packageJSON['types'] = packageJSON['types'].reduce((cleaned, type) => {
523 |         //Find the index in the "cleaned" array of the current type
524 |         const foundIndex = cleaned.findIndex(cleanedType => cleanedType['name'][0] === type['name'][0]);
525 |         //If it exists, then concat the members of cleaned and current
526 |         if (foundIndex > -1) {
527 |           cleaned[foundIndex]['members'] = [...cleaned[foundIndex]['members'],...type['members']];
528 |         }
529 |         //If not, then add it
530 |         else {
531 |           cleaned.push(type);
532 |         }
533 |         return cleaned;
534 |       },[]);
535 |     }
536 |   }
537 | 
538 |   
539 | }
540 | 


--------------------------------------------------------------------------------
/src/common/inventory.ts:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright (c) 2018, salesforce.com, inc.
  3 |  * All rights reserved.
  4 |  * SPDX-License-Identifier: BSD-3-Clause
  5 |  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
  6 |  */
  7 | import { incrementValue } from './JSONUtilities';
  8 | import {
  9 |   mdTypes,
 10 |   editionWarningRules,
 11 |   enablementRules,
 12 |   qualityRules,
 13 |   alertRules,
 14 |   minAPI,
 15 |   techAdoptionRules,
 16 |   dependencyRules,
 17 |   dataModels,
 18 |   standardNamespaces,
 19 |   rulesVersion
 20 | } from './rules';
 21 | 
 22 | //import { version } from './../../package.json';
 23 | 
 24 | 
 25 | export class packageInventory {
 26 |   private _mdInv = {};
 27 |   private _mdFullInvArray;
 28 |   private _mdMonitoredInvArray;
 29 |   private _minAPI = minAPI;
 30 |   private _enablementMessages;
 31 |   private _languageWarnings;
 32 |   private _alerts;
 33 |   private _installationWarnings;
 34 |   private _qualityRules;
 35 |   private _techAdoption;
 36 |   private _dependencies;
 37 | 
 38 | 
 39 |   public constructor() {
 40 |   };
 41 | 
 42 |   public setMinAPI(newAPIVersion) {
 43 |     this._minAPI = newAPIVersion;
 44 |   };
 45 | 
 46 |   public setMetadata(md) {
 47 |     this._mdInv = md;
 48 |     //Pre Calculate Dependencies
 49 |     //this.getDependencies();
 50 |   };
 51 | 
 52 | 
 53 |   private checkCondition(cond ) {
 54 |     let response = {
 55 |       conditionPass: false,
 56 |       passItems: [],
 57 |       failItems: []
 58 |     };
 59 |     if (!this.isConditionValid(cond)) {
 60 |       return response;
 61 |     }
 62 |       let mdTypeCount = this.getCountByMetadataType(cond.metadataType);
 63 |       if (mdTypeCount.length == 0) {
 64 |         mdTypeCount.push({
 65 |           property: cond.metadataType,
 66 |           value: -1
 67 |         });
 68 |       }
 69 |       //replace string 'minAPI' in operand with the minumum API specified
 70 |       if (Array.isArray(cond.operand)) {
 71 |         for (let i = 0; i < cond.operand.length; i++) {
 72 |           if (cond.operand[i] === 'minAPI') {
 73 |             cond.operand[i] = this._minAPI;
 74 |           }
 75 |         }
 76 |       }
 77 |       else {
 78 |         if (cond.operand === 'minAPI') {
 79 |           cond.operand = this._minAPI;
 80 |         }
 81 |       }
 82 |       let showDetails = false;
 83 |       if (cond.showDetails) {
 84 |         showDetails = true;
 85 |       }
 86 | 
 87 |       for (var itemCount of mdTypeCount) {
 88 |         let itemPass = false;
 89 |         switch (cond.operator) {
 90 |           case 'always':
 91 |             itemPass = true;
 92 |             break;
 93 |           case 'never':
 94 |             itemPass = false;
 95 |             break;
 96 |           case 'exists':
 97 |             itemPass = itemCount.value > 0;
 98 |             break;
 99 |           case 'notexists':
100 |             itemPass = itemCount.value <= 0;
101 |             break;
102 |           case 'null':
103 |             itemPass = itemCount.value < 0;
104 |             break;
105 |           case 'gt':
106 |             itemPass = cond.operand < itemCount.value;
107 |             break;
108 |           case 'gte':
109 |             itemPass = cond.operand <= itemCount.value;
110 |             break;
111 |           case 'lt':
112 |             itemPass = cond.operand > itemCount.value;
113 |             break;
114 |           case 'lte':
115 |             itemPass = cond.operand >= itemCount.value;
116 |             break;
117 |           case 'eq':
118 |             itemPass = cond.operand == itemCount.value;
119 |             break;
120 |           case 'between':
121 |             //Not inclusive
122 |             itemPass = (cond.operand[0] > itemCount.value) && (cond.operand[1] < itemCount.value);
123 |             break; 
124 |         }
125 |         if (itemPass) {
126 |           response.conditionPass = true;
127 |           response.passItems.push({name:itemCount.property,display:showDetails});
128 |         } else {
129 |           response.failItems.push({name:itemCount.property,display:showDetails});
130 |         }
131 |       }
132 |       if (cond.conditionOr != undefined) {
133 |         //By default, don't process the OR if the condition has already passed
134 |         if (cond.conditionOr.processAlways == true || !response.conditionPass) {
135 |           let orResponse = this.checkCondition(cond.conditionOr);
136 |           response.conditionPass = response.conditionPass || orResponse.conditionPass;
137 | 
138 |           response.passItems.push(...orResponse.passItems);
139 |           response.failItems.push(...orResponse.failItems);
140 |         }
141 |       }
142 |       if (cond.conditionAnd != undefined) {
143 |         //By default, don't process the AND if the condition is already false
144 |         if (cond.conditionAnd.processAlways == true || response.conditionPass) {
145 |           let andResponse = this.checkCondition(cond.conditionAnd);
146 |           if (cond.conditionAnd.conditionPerItem) {
147 |             //Condition passes if the same property passes in both this condition and in conditionAnd
148 |             let tmpItems = [];
149 |             for (const i of response.passItems) {
150 |               for (const ai of andResponse.passItems) {
151 |                 if (i.name == ai.name) {
152 |                   tmpItems.push ({name: i.name, display: i.display || ai.display});
153 |                   break;
154 |                 }
155 |               }
156 |             }
157 |             response.passItems = [...tmpItems];
158 |             //response.passItems = response.passItems.filter(item =>andResponse.passItems.includes(item));
159 |             //Logic correct here? Fail will return items that do not pass both conditions. Items that pass one condition but not the other are lost.
160 |             //response.failItems = response.failItems.filter(item => andResponse.failItems.includes(item));
161 |             tmpItems = [];
162 |             for (const i of response.failItems) {
163 |               for (const ai of andResponse.failItems) {
164 |                 if (i.name == ai.name) {
165 |                   tmpItems.push ({name: i.name, display: i.display || ai.display});
166 |                   break;
167 |                 }
168 |               }
169 |             }
170 |             response.failItems = [...tmpItems];
171 |             response.conditionPass = response.passItems.length > 0;
172 |           } else {
173 |             response.conditionPass = response.conditionPass && andResponse.conditionPass;
174 |             response.passItems.push(...andResponse.passItems);
175 |             response.failItems.push(...andResponse.failItems);
176 |           }
177 |         }
178 |       }
179 |     
180 |     return response;
181 |   }
182 | 
183 |   private isConditionValid(cond) {
184 |     let isValid = true;
185 |     let validOperators = ['always', 'never', 'exists', 'notexists', 'null', 'gt', 'gte', 'lt', 'lte', 'eq','between'];
186 |     let operatorsNeedOperand = ['gt', 'gte', 'lt', 'lte', 'eq'];
187 |     
188 |     //Check Expiration
189 |     if (cond['expiration'] != undefined) {
190 |       const now = new Date().toJSON();
191 |       if (cond.expiration < now) {
192 |         isValid = false;
193 |       }
194 |     }
195 |     //Cannot have both conditionAnd and conditionOr properties
196 |     if (cond['conditionAnd'] != undefined && cond['conditionOr'] != undefined) {
197 |       isValid = false;
198 |     }
199 |     //Make sure Operator is valid
200 |     if (!validOperators.includes(cond['operator'])) {
201 |       isValid = false;
202 |     }
203 |     //Make sure Operators that require a comparitor have one
204 |     if (operatorsNeedOperand.includes(cond['operator']) && isNaN(cond['operand'])) {
205 |       isValid = false;
206 |     }
207 | 
208 |     //Make sure 'between' has 2 operands and that the first is the lowest
209 |     if (cond.operator === 'between') {
210 |       if (Array.isArray(cond.operand) && cond.operand.length == 2) {
211 |         cond.operand.sort();
212 |       }
213 |       else {
214 |         isValid = false;
215 |       }
216 |     }
217 | 
218 |     //We need a metadata type unless operator is always or never
219 |     if ((cond.operator != 'always' && cond.operator != 'never') && cond.metadataType == undefined) {
220 |       isValid = false;
221 |     }
222 |     return isValid;
223 |   }
224 | 
225 | 
226 |   private isRuleValid(rule) {
227 | 
228 |     return (rule['name'] != undefined &&
229 |             rule['condition'] != undefined &&
230 |             (rule['resultTrue'] != undefined || rule['resultFalse'] != undefined));
231 |   }
232 | 
233 |   public getDependencies() {
234 |     if (!this._dependencies) {
235 |       this._dependencies = [];
236 |      
237 |       let dependencyRulesConstructed = [...dependencyRules];
238 |       for (var dataModel of dataModels) {
239 |         
240 |         let conditions = [];
241 |         //Check Namespaces
242 |         if (dataModel.namespaces) {
243 |           for (var ns of dataModel.namespaces) {
244 |             conditions.push({
245 |               metadataType: `dependencies.namespaces.${ns}`,
246 |               operator: 'exists'
247 |             });
248 |           }
249 |   
250 |         }
251 |         //Check Additional Metadata Types
252 |         if (dataModel.metadataTypes) {
253 |           for (var mdt of dataModel.metadataTypes) {
254 |             conditions.push({metadataType: `${mdt}`,operator:'exists'});
255 |           }
256 |         }
257 |         //Check Objects
258 |         for (var objName of dataModel.objects) {
259 |           //create condition for Object references in custom fields
260 |           conditions.push({
261 |             metadataType: `CustomField.objects.${objName}`,
262 |             operator: 'exists'
263 |           });
264 |           //create condition of PBs on the object
265 |           conditions.push({
266 |             metadataType: `Flow.objects.${objName}`,
267 |             operator: 'exists'
268 |           });
269 |           //create condition of triggers on the object
270 |           conditions.push({
271 |             metadataType: `ApexTrigger.objects.${objName}`,
272 |             operator: 'exists'
273 |           });
274 |           //create condition of triggers on the object
275 |           conditions.push({
276 |             metadataType: `dependencies.components.${objName}`,
277 |             operator: 'exists'
278 |           });
279 |         }
280 |         if (conditions.length > 0) {
281 |           let conditionConstructed = conditions.pop();
282 |           let nextCondition;
283 |           while (nextCondition = conditions.pop()) {
284 |             let prevCondition = Object.assign({},conditionConstructed);
285 |             conditionConstructed = Object.assign({},nextCondition);
286 |             conditionConstructed['conditionOr'] = prevCondition;
287 |           }
288 |           let dependencyConstructed = {
289 |             name: dataModel.name,
290 |             label: dataModel.label,
291 |             condition: conditionConstructed
292 |           };
293 |           dependencyRulesConstructed.push(dependencyConstructed) 
294 |         }
295 |       }
296 |       for (var dependencyRule of dependencyRulesConstructed) {
297 |         if (this.isConditionValid(dependencyRule.condition)) {
298 |           if (this.checkCondition(dependencyRule.condition).conditionPass) {
299 |             this._dependencies.push({
300 |               name: dependencyRule.name,
301 |               label: dependencyRule.label
302 |             })
303 |             incrementValue(this._mdInv,`dependencies.${dependencyRule.name}`);
304 |           };
305 |         }
306 |       }
307 | 
308 |      let nameSpaces = this.getNamespaceReferences();
309 |      if (nameSpaces.size > 0) {
310 |        this._dependencies.push({
311 |          name: 'namespaces',
312 |          label: 'Namespaces:',
313 |          items: Array.from(nameSpaces)
314 |        });
315 |      }
316 |     }
317 |     return this._dependencies;
318 |   }
319 | 
320 |   private getNamespaceReferences() {
321 |     let nameSpaces = new Set();
322 |     let nameSpaceResults = this.checkCondition({metadataType:'dependencies.namespaces.*',operator:'exists'});
323 |     for (var ns of nameSpaceResults.passItems) {
324 |       nameSpaces.add(ns.name);
325 |     }
326 |     //remove standard namespaces
327 |     for (const ns of standardNamespaces) {
328 |       nameSpaces.delete(ns);
329 |     }
330 |   
331 |     return nameSpaces;
332 |   }
333 | 
334 | 
335 |   public processRules(ruleSet) {
336 |     //Replaces   public checkRules(ruleSet) 
337 |     let results = [];
338 |     for (var rule of ruleSet) {
339 |       if (!this.isRuleValid(rule)) {
340 |         continue;
341 |       }
342 |       let conditionResult = this.checkCondition(rule.condition);
343 |       if (conditionResult.conditionPass && rule.resultTrue != undefined) {
344 |         let result = {};
345 |         result['label'] = rule.resultTrue.label;
346 |         result['message'] = rule.resultTrue.message;
347 |         if (rule.resultTrue.url != undefined) {
348 |           result['url'] = rule.resultTrue.url;
349 |         }
350 |         if (rule.resultTrue.showDetails) {
351 |          result['exceptions'] = [];
352 |          for (const i of conditionResult.passItems) {
353 |            if (i.display) {
354 |              result['exceptions'].push(i.name);
355 |            }
356 |          }
357 |         }
358 |         results.push(result);
359 |       }
360 |       if (!conditionResult.conditionPass && rule.resultFalse != undefined) {
361 |         let result = {};
362 |         result['label'] = rule.resultFalse.label;
363 |         result['message'] = rule.resultFalse.message;
364 |         if (rule.resultFalse.url != undefined) {
365 |           result['url'] = rule.resultFalse.url;
366 |         }
367 |         if (rule.resultFalse.showDetails) {
368 |           result['exceptions'] = [];
369 |           for (const i of conditionResult.failItems) {
370 |             if (i.display) {
371 |               result['exceptions'].push(i.name);
372 |             }
373 |           }
374 |         }
375 |         results.push(result);
376 |       }
377 |     }
378 |     return results;
379 |   };
380 | 
381 |   public getInstallationWarnings() {
382 |     if (!this._installationWarnings) {
383 |       this._installationWarnings = [];
384 |       for (var edition of editionWarningRules) {
385 |         let editionBlock = [];
386 |         for (var blockingItem of edition.blockingItems) {
387 |           let result = this.checkCondition(blockingItem.condition);
388 |           if (result.conditionPass) {
389 |             editionBlock.push({
390 |               label: blockingItem.label,
391 |   
392 |             })
393 |           }
394 |         }
395 |         if (editionBlock.length > 0) {
396 |           this._installationWarnings.push( {
397 |             'edition': edition.name,
398 |             blockingItems: editionBlock
399 |           });
400 |         }
401 |       }
402 |   
403 |     }
404 |     return this._installationWarnings;
405 |   };
406 | 
407 |   public getTechAdoptionScore() {
408 |     if (!this._techAdoption) {
409 |     this._techAdoption = [];
410 |     for (var adoptionRule of techAdoptionRules) {
411 |       let categoryResult = {
412 |         categoryName: adoptionRule.categoryName,
413 |         categoryLabel: adoptionRule.categoryLabel,
414 |         technologies: [],
415 |         points: 0
416 |       };
417 |       for (var tech of adoptionRule.technologies) {
418 | 
419 |         let techResult = {
420 |           name: tech.name,
421 |           question: tech.question,
422 |           points: 0,
423 |           maxPoints: tech.points,
424 |           found: false,
425 |           levelUp: undefined,
426 |           detectable: tech.hasOwnProperty('condition')
427 |         };
428 | 
429 |         if (techResult.detectable) {
430 |           let conditionResult = this.checkCondition(tech.condition);
431 |           techResult.found = conditionResult.conditionPass;
432 |         }
433 | 
434 |         techResult.points = techResult.found ? tech.points : 0; 
435 |         techResult.levelUp = tech.levelUp; 
436 |   /*      if (!techResult.found && tech.levelUp != undefined) {
437 |             techResult['levelUp'] = tech.levelUp;
438 |         }
439 |     */      
440 |         categoryResult.technologies.push(techResult);
441 |         categoryResult.points += techResult.points;
442 |         }
443 | 
444 |         
445 |       this._techAdoption.push(categoryResult);
446 |     }
447 |   }  
448 |     return this._techAdoption;
449 |   
450 |   }
451 | 
452 |   public getAlerts() {
453 |     if (!this._alerts) {
454 |       this._alerts = this.processRules(alertRules);
455 |     }
456 |     return this._alerts;
457 |   }
458 | 
459 |   public getQualityRecommendations() {
460 |     if (!this._qualityRules) {
461 |       this._qualityRules = this.processRules(qualityRules);
462 |     }
463 |     return this._qualityRules;
464 |   };
465 | 
466 |   public getEnablementMessages() {
467 |     if (!this._enablementMessages) {
468 |       this._enablementMessages = this.processRules(enablementRules);
469 |     }
470 |     return this._enablementMessages;
471 |   };
472 | 
473 |   public getLanguageWarnings() {
474 |     if (!this._languageWarnings) {
475 |       this._languageWarnings = this._mdInv['language'];
476 |     }
477 | 
478 |     return this._languageWarnings;
479 |   };
480 | 
481 |   public getCountByMetadataType(metadataType) {
482 |     let mdDefArray = metadataType.split('.');
483 |     let retVal = [];
484 |     let mdCount = this.traverseMetadata(mdDefArray, this._mdInv);
485 |     if (Array.isArray(mdCount)) {
486 |       retVal = mdCount;
487 |     } else {
488 |       retVal.push(mdCount);
489 |     }
490 |     return retVal;
491 |   };
492 | 
493 |   public traverseMetadata(mdArray, mdObject, wildcard = '') {
494 |     //  Recurses through mdArray -- a sequential list of properties to navigate down the object, mdObject
495 |     //
496 |     // Check the first element in the array. 
497 |     // If it's a wildcard (*), go through all top level properties of the object calling this function again for each property of the object 
498 |     // If it is not a wildcard, check to see if that value exists as a key of the object.
499 |     // If value exists and there exist more entries in the properties array, recursively call this function with parameters mdArray = (original mdArray with first entry removed), mdObject = mdObject['Property that was removed from mdArray']
500 |     // if value exists and there are no more entries in the array, check to see if the value is a number
501 |     // If it is a number, then return the number and the property name (or the wildcard name)
502 |     // If it is not a number, then check to see if adding .count to the property is a number
503 |     let topLevel = mdArray.shift();
504 |     if (topLevel === '*') {
505 |       let retVal = [];
506 | 
507 |       for (var topArray in mdObject) {
508 |         let tmpArray = [topArray, ...mdArray];
509 |         let tmpResult = this.traverseMetadata(tmpArray, mdObject, topArray);
510 |         if (Array.isArray(tmpResult)) {
511 |           retVal = retVal.concat(tmpResult);
512 |         }
513 |         else {
514 |           retVal.push(tmpResult);
515 |         }
516 |         
517 |       }
518 |       return retVal;
519 |     } else if (mdObject[topLevel] != undefined) {
520 |       if (mdArray.length > 0) {
521 |         if (mdArray.length == 1 && mdArray[0] === '*') {
522 |           //Check to see if the wildcard object has any values
523 |           if (Object.keys(mdObject[topLevel]).length == 0) {
524 |             return {
525 |               property: topLevel,
526 |               value: -1
527 |             };
528 |           }
529 |         }
530 |         return this.traverseMetadata(mdArray, mdObject[topLevel], wildcard);
531 |       } else {
532 |         let count = undefined;
533 |         if (isNaN(mdObject[topLevel])) {
534 |           if (mdObject[topLevel]['count'] != undefined && isFinite(mdObject[topLevel]['count'])) {
535 |             count = mdObject[topLevel]['count'];
536 |           } else if (mdObject[topLevel] === Object(mdObject[topLevel])) {
537 |             count = Object.keys(mdObject[topLevel]).length;
538 |           } else if (Array.isArray(mdObject[topLevel])) {
539 |             count = mdObject[topLevel].length;
540 |           }
541 |           else {
542 |             return {};
543 |           }
544 | 
545 |         } else {
546 |           count = mdObject[topLevel];
547 |         }
548 |         let componentName = wildcard == '' ? topLevel : wildcard;
549 |         return {
550 |           property: componentName,
551 |           value: count
552 |         };
553 |       }
554 | 
555 |     } else {
556 |       return {
557 |         property: topLevel,
558 |         value: -1
559 |       };
560 |     }
561 |   };
562 | 
563 |   public getJSONOutput() {
564 |     const date = new Date();
565 | 
566 |     let retVal = {};
567 |     retVal['Status'] = {
568 |       "Date": date.toUTCString(),
569 | //      "PluginVersion": version,
570 |       "RulesVersion": rulesVersion
571 |     };
572 |     retVal['MetadataInventory'] = this._mdInv;
573 |     retVal['MonitoredItems'] = this.getMonitoredInvArray();
574 |     retVal['Recommendations'] = this.getEnablementMessages();
575 |     retVal['CodeQualityNotes'] = this.getQualityRecommendations();
576 |     retVal['InstallationWarnings'] = this.getInstallationWarnings();
577 |     retVal['Alerts'] = this.getAlerts();
578 |     retVal['AdoptionScore'] = this.getTechAdoptionScore();
579 |     retVal['Dependencies'] = this.getDependencies();
580 |     retVal['LanguageWarnings'] = this.getLanguageWarnings();
581 |     return retVal;
582 |   };
583 | 
584 | 
585 |   public getFullInvArray() {
586 |     if (!this._mdFullInvArray) {
587 |       this._mdFullInvArray = [];
588 |       for (var mdType in this._mdInv) {
589 |         if (mdType !== 'apiVersions') {
590 |           this._mdFullInvArray.push({
591 |             "metadataType": mdType,
592 |             "count": this._mdInv[mdType]["count"]
593 |           });
594 |         }
595 |       };
596 |     }
597 | 
598 |     return this._mdFullInvArray;
599 |   };
600 | 
601 |   public getMonitoredInvArray() {
602 |     if (!this._mdMonitoredInvArray) {
603 |       this._mdMonitoredInvArray = [];
604 |       mdTypes.forEach(element => {
605 |         let retObj = {};
606 |         let extras = [];
607 |         let extrasCustom = [];
608 |         let extrasStandard = [];
609 |         let count = 0;
610 |         retObj['metadataType'] = element.metadataType;
611 |         retObj['label'] = element.label;
612 |         if (element.metadataType) {
613 |           switch (String(element.metadataType)) {
614 | 
615 |             case 'CustomField':
616 |               if (this._mdInv[element.metadataType]) {
617 |                 count = this._mdInv[element.metadataType]['count'];
618 |                 const objects = Object.keys(this._mdInv[element.metadataType]['objects']);
619 |                 let standardObjFields = {
620 |                   metadataSubType: 'StandardObjectFields',
621 |                   label: '  Total Fields on Standard Objects',
622 |                   'count': 0
623 |                 };
624 |                 let customObjFields = {
625 |                   metadataSubType: 'CustomObjectFields',
626 |                   label: '  Total Fields on Custom Objects',
627 |                   'count': 0
628 |                 };
629 | 
630 |                 for (const obj of objects) {
631 |                   let objFieldCount = this._mdInv[element.metadataType]['objects'][obj]['count'];
632 | 
633 | 
634 |                   if (this._mdInv[element.metadataType]['objects'][obj]['objectType'] === 'Custom') {
635 |                     customObjFields['count'] += objFieldCount;
636 |                     extrasCustom.push({
637 |                       metadataSubType: `object.${obj}`,
638 |                       label: `   Fields on ${obj}`,
639 |                       'count': objFieldCount
640 |                     });
641 |                   } else {
642 |                     standardObjFields['count'] += objFieldCount;
643 |                     extrasStandard.push({
644 |                       metadataSubType: `object.${obj}`,
645 |                       label: `   Fields on ${obj}`,
646 |                       'count': objFieldCount
647 |                     });
648 |                   }
649 |                 }
650 |                 extras.push(standardObjFields);
651 |                 extras = extras.concat(extrasStandard);
652 |                 extras.push(customObjFields);
653 |                 extras = extras.concat(extrasCustom);
654 | 
655 |               }
656 |               break;
657 |             case 'CustomApplication':
658 |               if (this._mdInv[element.metadataType]) {
659 |                 count = this._mdInv[element.metadataType]['count'];
660 |                 extras.push({
661 |                   metadataSubType: 'LightningApp',
662 |                   label: '  Lighting Applications',
663 |                   count: this._mdInv[element.metadataType]['LightingAppCount']
664 |                 });
665 |                 extras.push({
666 |                   metadataSubType: 'LigthingConsole',
667 |                   label: '   Lighting Consoles',
668 |                   count: this._mdInv[element.metadataType]['LightningConsoleCount']
669 |                 });
670 |                 extras.push({
671 |                   metadataSubType: 'ClassicApp',
672 |                   label: '  Classic Applications',
673 |                   count: this._mdInv[element.metadataType]['ClassicAppCount']
674 |                 });
675 |                 extras.push({
676 |                   metadataSubType: 'ClassicConsole',
677 |                   label: '   Classic Consoles',
678 |                   count: this._mdInv[element.metadataType]['ClassicConsoleCount']
679 |                 });
680 |               }
681 |               break;
682 |             case 'ApexClass':
683 |               if (this._mdInv[element.metadataType]) {
684 |                 count = this._mdInv[element.metadataType]['count'];
685 |                 extras.push({
686 |                   metadataSubType: 'ApexFuture',
687 |                   label: '  With Future Methods',
688 |                   count: this._mdInv[element.metadataType]['FutureCalls']
689 |                 });
690 |                 extras.push({
691 |                   metadataSubType: 'AuraEnabled',
692 |                   label: '  With Aura Enabled Methods',
693 |                   count: this._mdInv[element.metadataType]['AuraEnabledCalls']
694 |                 });
695 |                 extras.push({
696 |                   metadataSubType: 'InvocableApex',
697 |                   label: '  With Invocable Methods or Variables',
698 |                   count: this._mdInv[element.metadataType]['InvocableCalls']
699 |                 });
700 |                 extras.push({
701 |                   metadataSubType: 'BatchApex',
702 |                   label: '  Batch Apex',
703 |                   count: this._mdInv[element.metadataType]['BatchApex']
704 |                 });
705 |                 extras.push({
706 |                   metadataSubType: 'ApexRest',
707 |                   label: '  Apex REST',
708 |                   count: this._mdInv[element.metadataType]['ApexRest']
709 |                 });
710 |                 extras.push({
711 |                   metadataSubType: 'ApexSoap',
712 |                   label: '  SOAP Web Services',
713 |                   count: this._mdInv[element.metadataType]['ApexSoap']
714 |                 });
715 |                 extras.push({
716 |                   metadataSubType: 'SchedulableApex',
717 |                   label: '  Schedulable Apex',
718 |                   count: this._mdInv[element.metadataType]['SchedulableApex']
719 |                 });
720 |                 extras.push({
721 |                   metadataSubType: 'TestClasses',
722 |                   label: '  Test Classes',
723 |                   count: this._mdInv[element.metadataType]['TestClasses']
724 |                 });
725 |                 extras.push({
726 |                   metadataSubType: 'CharacterCount',
727 |                   label: '  Total Apex Characters',
728 |                   count: this._mdInv[element.metadataType]['CharacterCount']
729 |                 });
730 | 
731 |               }
732 |               break;
733 |             case 'Flow':
734 |               if (this._mdInv[element.metadataType]) {
735 |                 count = this._mdInv[element.metadataType]['count'];
736 |                 const flowTypes = Object.keys(this._mdInv[element.metadataType]['FlowTypes']);
737 |                 for (const flowType of flowTypes) {
738 |                   let ftLabel;
739 |                   const ftCount =  this._mdInv[element.metadataType]['FlowTypes'][flowType]['count'];
740 |                   switch (flowType) {
741 |                     case 'Flow':
742 |                       ftLabel = '  Screen Flows';
743 |                       break;
744 |                     case 'AutoLaunchedFlow':
745 |                       ftLabel = '  Autolaunched Flows';
746 |                       break;
747 |                     case 'Workflow':
748 |                       ftLabel = '  Process Builder';
749 |                       break;
750 |                     default:
751 |                       ftLabel = `  ${flowType}`;
752 |                   }
753 |                   extras.push({
754 |                     metadataSubType: flowType,
755 |                     label: ftLabel,
756 |                     count: ftCount
757 |                   });
758 |                 }
759 |                
760 |                 const objects = Object.keys(this._mdInv[element.metadataType]['objects']);
761 |                 for (const obj of objects) {
762 |                   let objPBTriggerCount = this._mdInv[element.metadataType]['objects'][obj]['count'];
763 |                   extras.push({
764 |                     metadataSubType: `object.${obj}`,
765 |                     label: `    Process Builders on ${obj}`,
766 |                     'count': objPBTriggerCount
767 |                   });
768 | 
769 |                 }
770 |                 extras.push({
771 |                   metadataSubType: 'FlowTemplate',
772 |                   label: '  Flow Templates',
773 |                   count: this._mdInv[element.metadataType]['FlowTemplate']
774 |                 });
775 |                 const flowTemplateTypes = Object.keys(this._mdInv[element.metadataType]['FlowTemplates']);
776 |                 for (const flowTemplateType of flowTemplateTypes) {
777 |                   let ftLabel;
778 |                   const ftCount =  this._mdInv[element.metadataType]['FlowTemplates'][flowTemplateType]['count'];
779 |                   switch (flowTemplateType) {
780 |                     case 'Flow':
781 |                       ftLabel = '    Screen Flow Templates';
782 |                       break;
783 |                     case 'AutoLaunchedFlow':
784 |                       ftLabel = '    Autolaunched Flow Templates';
785 |                       break;
786 |                     default:
787 |                       ftLabel = `    ${flowTemplateType}`;
788 |                   }
789 |                   extras.push({
790 |                     metadataSubType: `${flowTemplateType}Template`,
791 |                     label: ftLabel,
792 |                     count: ftCount
793 |                   });
794 |                 }
795 |                 
796 |               }
797 |               break;
798 |             case 'ConnectedApp':
799 |               if (this._mdInv[element.metadataType]) {
800 |                 count = this._mdInv[element.metadataType]['count'];
801 |                 extras.push({
802 |                   metadataSubType: 'CanvasApp',
803 |                   label: '  Canvas Apps',
804 |                   count: this._mdInv[element.metadataType]['CanvasApp']
805 |                 });
806 |               }
807 |               break;
808 |             case 'CustomObject':
809 |               if (this._mdInv[element.metadataType]) {
810 |                 count = this._mdInv[element.metadataType]['count'];
811 |                 extras.push({
812 |                   metadataSubType: 'BigObject',
813 |                   label: '  Big Objects',
814 |                   count: this._mdInv[element.metadataType]['BigObject']
815 |                 });
816 |                 extras.push({
817 |                   metadataSubType: 'ExternalObject',
818 |                   label: '  External Objects',
819 |                   count: this._mdInv[element.metadataType]['ExternalObject']
820 |                 });
821 | 
822 | 
823 |               }
824 |               break;
825 |             case 'ApexTrigger':
826 |               if (this._mdInv[element.metadataType]) {
827 |                 count = this._mdInv[element.metadataType]['count'];
828 |                 extras.push({
829 |                   metadataSubType: 'AsyncTrigger',
830 |                   label: '  Async Triggers',
831 |                   count: this._mdInv[element.metadataType]['AsyncTrigger']
832 |                 });
833 |                 extras.push({
834 |                   metadataSubType: 'CharacterCount',
835 |                   label: '  Total Apex Characters',
836 |                   count: this._mdInv[element.metadataType]['CharacterCount']
837 |                 });
838 |                 const objects = Object.keys(this._mdInv[element.metadataType]['objects']);
839 |                 for (const obj of objects) {
840 |                   let objTriggerCount = this._mdInv[element.metadataType]['objects'][obj]['count'];
841 |                   extras.push({
842 |                     metadataSubType: `object.${obj}`,
843 |                     label: `  Triggers on ${obj}`,
844 |                     'count': objTriggerCount
845 |                   });
846 | 
847 |                 }
848 |               }
849 |               break;
850 |             case 'LightningComponentBundle':
851 |               if (this._mdInv[element.metadataType]) {
852 |                 count = this._mdInv[element.metadataType]['count'];
853 |                 extras.push({
854 |                   metadataSubType: 'ExposedComponents',
855 |                   label: '  Exposed Components',
856 |                   count: this._mdInv[element.metadataType]['ExposedComponents']
857 |                 });
858 |                 
859 |                 for (var [target,targetCount] of Object.entries(this._mdInv[element.metadataType]['targets'])) {
860 |                   let friendlyName = target.replace(/lightning(__)?/g,'');
861 |                   extras.push({
862 |                     metadataSubType: target,
863 |                     label: `  ${friendlyName}`,
864 |                     count: targetCount
865 |                   });
866 |                 }
867 |              
868 |               }
869 |               break;
870 |             default:
871 |               if (this._mdInv[element.metadataType]) {
872 |                 count = this._mdInv[element.metadataType]['count'];
873 |               }
874 |           }
875 | 
876 |         } else {
877 |           count = -1
878 |         };
879 |         retObj['count'] = count;
880 |         this._mdMonitoredInvArray.push(retObj);
881 |         if (extras.length > 0) {
882 |           this._mdMonitoredInvArray = this._mdMonitoredInvArray.concat(extras);
883 |         }
884 |       });
885 |     }
886 |     return this._mdMonitoredInvArray;
887 |   };
888 | 
889 | };
890 | 


--------------------------------------------------------------------------------
/src/common/metadataScan.ts:
--------------------------------------------------------------------------------
   1 | 
   2 | import {
   3 |   SfdxError
   4 | } from '@salesforce/core';
   5 | 
   6 | import alex = require('alex');
   7 | 
   8 | import {
   9 |   setValue,
  10 |   incrementValue,
  11 |   getValue,
  12 |   addValue
  13 | } from './JSONUtilities';
  14 | 
  15 | import {
  16 |   mdmap
  17 | } from './mdmap';
  18 | 
  19 | import fs = require('fs-extra');
  20 | 
  21 | import xml2js = require('xml2js');
  22 | 
  23 | import {alexConfig} from './rules';
  24 | 
  25 | 
  26 | export function inventoryPackage(sourceDir, p, options = {}) {
  27 |   let types = p.types;
  28 |   let inventory = {};
  29 |   let apiVersions = {};
  30 |   let componentProperties = {};
  31 |   let dependencies = {};
  32 |   let language = {};
  33 |   let tmp; //just a temp variable for some ugly if magic
  34 |   let scanLanguage = options['scanLanguage'] ?  true: false;
  35 |   
  36 |   if (p.version) {
  37 |     apiVersions['mdapi'] = parseFloat(p.version[0]);
  38 |   }
  39 |   for (var typeIdx in types) {
  40 |     let metadataType = types[typeIdx]['name'];
  41 |     
  42 |     let typeInv = {};
  43 | 
  44 |     //typeInv['index'] = typeIdx;
  45 | 
  46 |   
  47 |     //Check for wildcard members
  48 |     if (types[typeIdx]['members'].includes('*')) {
  49 |       types[typeIdx]['members'] = getMembers(types[typeIdx], sourceDir);
  50 |     }
  51 |     typeInv['count'] = types[typeIdx]['members'].length;
  52 | 
  53 | 
  54 |     switch (String(metadataType)) {
  55 |       case 'CustomField':
  56 |         //Do per object field counts
  57 |         let objectFields = {};
  58 | 
  59 |         for (var fieldIdx in types[typeIdx]['members']) {
  60 |           let fieldFullName = types[typeIdx]['members'][fieldIdx];
  61 |           let object = getNameSpaceAndType(fieldFullName.split(".")[0]);
  62 |           let field = getNameSpaceAndType(fieldFullName.split(".")[1]);
  63 |           addObjectDependencies(dependencies,[object,field]);
  64 |           if (objectFields[object.fullName]) {
  65 |             objectFields[object.fullName]['count'] += 1;
  66 |           } else {
  67 |             objectFields[object.fullName] = {
  68 |               'count': 1,
  69 |               'objectType': object.type
  70 |             };
  71 |           }
  72 | 
  73 |           //Check field descriptions
  74 |           //Only check custom fields or standard fields on custom objects, not standard
  75 |           //Never check for namespaced fields
  76 |           if (field.namespace == null && ((object.type == 'Standard' && field.type !== 'Standard')|| object.type !== 'Standard')) {
  77 |             const objectPath = `${sourceDir}/objects`;
  78 |             let objectXml = `${objectPath}/${object.fullName}.object`;
  79 |             
  80 |             let objectJSON = parseXML(objectXml);
  81 |             if (objectJSON['CustomObject'] && objectJSON['CustomObject']['fields']) {
  82 |               for (var fieldDef of objectJSON['CustomObject']['fields']) {
  83 |                 if (fieldDef['fullName'] == field.fullName) {
  84 |                   let descExists = fieldDef['description'] ? 1 : 0;
  85 |                   setValue(componentProperties,`CustomField.${fieldFullName.replace(/\./g,"->")}.descriptionExists`,descExists);
  86 |             
  87 |                   if (scanLanguage && (tmp = languageScanMetadataObject(fieldDef))) {
  88 |                     setValue(language,`CustomField.${fieldFullName.replace(/\./g,"->")}`,tmp);
  89 |                   }
  90 |                 }
  91 | 
  92 |               }
  93 |             }
  94 |           }
  95 |         }
  96 |         typeInv['objects'] = objectFields;
  97 | 
  98 |         break;
  99 |       case 'CustomObject':
 100 |         // Look for Custom Settings, External Objects,  Person Accounts, Big Objects
 101 |         const objectPath = `${sourceDir}/objects`;
 102 |         let xoCount = 0;
 103 |         let boCount = 0;
 104 |         let csType = {
 105 |           count: 0
 106 |         };
 107 |         let peType = {
 108 |           count: 0
 109 |         };
 110 |         let fmType = {
 111 |           count: 0
 112 |         };
 113 |         let cmType = {
 114 |           count:0
 115 |         };
 116 |         for (var objIdx in types[typeIdx]['members']) {
 117 |           let object = getNameSpaceAndType(types[typeIdx]['members'][objIdx]);
 118 |           addObjectDependencies(dependencies,[object]);
 119 |           //Check external Objects
 120 |           if (object.extension == 'x') {
 121 |             xoCount += 1;
 122 |           }
 123 |           //Check Big Objects
 124 |           if (object.extension == 'b') {
 125 |             boCount += 1;
 126 |           }
 127 | 
 128 |           //Check Platform Events
 129 |           if (object.extension == 'e') {
 130 |             peType['count'] += 1;
 131 |             typeInv['count'] -= 1;
 132 |           }
 133 |           //Check CustomMetadata Events
 134 |           if (object.extension == 'mdt') {
 135 |             cmType['count'] += 1;
 136 |             typeInv['count'] -= 1;
 137 |           }
 138 |           //Check Feature Management Parameters
 139 |           if (String(object.fullName).includes('FeatureParameter')) {
 140 |             fmType['count'] += 1;
 141 |             typeInv['count'] -= 1;
 142 |           }
 143 | 
 144 |           let objectXml = `${objectPath}/${object.fullName}.object`;
 145 |           let objectJSON = parseXML(objectXml);
 146 |           //Dive Deeper
 147 |           if (objectJSON['CustomObject']) {
 148 |             //Check Custom Settings
 149 |             if (objectJSON['CustomObject']['customSettingsType']) {
 150 |               csType['count'] += 1;
 151 |               typeInv['count'] -= 1;
 152 |             }
 153 | 
 154 |             //Check for Descriptions
 155 |           if (object.type == 'Custom' && object.namespace == null) {
 156 |             let descExists = getValue(objectJSON,'CustomObject.description',null) ? 1 : 0;
 157 |             setValue(componentProperties,`CustomObject.${object.fullName}.descriptionExists`,descExists);
 158 | 
 159 |           }
 160 | 
 161 |           //LanguageScan
 162 |           
 163 |           if (scanLanguage && (tmp = languageScanMetadataObject(objectJSON))) {
 164 |             setValue(language,`CustomObject.${object.fullName}`,tmp);
 165 |           }
 166 |         
 167 |           }
 168 |           
 169 |           
 170 |          
 171 |         }
 172 |         typeInv['BigObject'] = boCount;
 173 |         typeInv['ExternalObject'] = xoCount;
 174 |         inventory['CustomSetting__c'] = csType;
 175 |         inventory['PlatformEvent__c'] = peType;
 176 |         inventory['FeatureManagement__c'] = fmType;
 177 |         inventory['CustomMetadata'] = cmType;
 178 | 
 179 | 
 180 |         break;
 181 |       case 'PlatformEventChannel':
 182 |         //Look for ChangeDataEvents
 183 |         break;
 184 |       case 'Flow':
 185 |         let templateCount = 0;
 186 |         let objects = {};
 187 |         let flowTypes = {};
 188 |         let flowTemplates = {};
 189 |         const flowPath = `${sourceDir}/flows`;
 190 |         for (var flowIdx in types[typeIdx]['members']) {
 191 |           let flowName = types[typeIdx]['members'][flowIdx];
 192 |           let flowXml = `${flowPath}/${flowName}.flow`;
 193 |           let flowJSON = parseXML(flowXml);
 194 |           let processType = getValue(flowJSON,'Flow.processType','UnknownType');
 195 |           incrementValue(flowTypes,`${processType}.count`);
 196 |           if (getValue(flowJSON,'Flow.isTemplate.0',false)) {
 197 |             templateCount += 1;
 198 |             incrementValue(flowTemplates,`${processType}.count`);
 199 |           }
 200 |           //Language Scan
 201 |           if (scanLanguage && (tmp = languageScanMetadataObject(flowJSON))) {
 202 |             setValue(language, `Flow.${flowName}`,tmp);
 203 |           }
 204 |           addObjectDependencies(dependencies, extractObjectsApex(JSON.stringify(flowJSON)));
 205 | 
 206 |           //Do per object Inventory
 207 |           let processMetadataValues;
 208 |           if (processMetadataValues = getValue(flowJSON,'Flow.processMetadataValues',null)) {
 209 |               for (var processMetadataValue of processMetadataValues) {
 210 |                 if (processMetadataValue['name'] == 'ObjectType') {
 211 |                   let objectName = processMetadataValue['value'][0]['stringValue'][0];
 212 |                   let object = getNameSpaceAndType(objectName);
 213 |                   addObjectDependencies(dependencies,[object]);
 214 |                   incrementValue(objects,`${objectName}.count`);
 215 |                 }
 216 |               }
 217 |             }
 218 |             
 219 |             for (var url of findHardcodedURLs(flowXml)){
 220 |               let escapedURL = url.replace(/(? 0) {
 585 |               referencedComponents.forEach(element => {
 586 |                 let ns = element.split(":", 2)[0];
 587 |                 incrementValue(componentProperties,`ApexPage.${vfName}.namespaceReferences.${ns}`);
 588 |                 setValue(dependencies,`namespaces.${ns}`,1);
 589 |               });
 590 |             }
 591 | 
 592 |             for (var url of findHardcodedURLs(vfBody)){
 593 |               let escapedURL = url.replace(/(? 0) {
 628 |               referencedComponents.forEach(element => {
 629 |                 let ns = element.split(":", 2)[0];
 630 |                 incrementValue(componentProperties,`AuraDefinitionBundle.${auraName}.namespaceReferences.${ns}`);
 631 |                 //Also add it to the Namespaces dependencies
 632 |                 setValue(dependencies,`namespaces.${ns}`,1);
 633 |               });
 634 |             }
 635 |             const interfaceReg = /(?:implements|extends)\s*=\s*"([\w ,:]+)"/igm;
 636 |             let interfaceMatches = getMatches(auraBody, interfaceReg);
 637 |             if (interfaceMatches.length > 0) {
 638 |               interfaceMatches.forEach(element => {
 639 |                 let interfaces = element.split(/ *, */);
 640 |                 interfaces.forEach(element => {
 641 |                   setValue(componentProperties,`AuraDefinitionBundle.${auraName}.interfaces.${element}`,1);
 642 |                 });
 643 |               })
 644 |             }
 645 |             //Find Object References
 646 |             addObjectDependencies(dependencies,extractObjectsApex(auraBody));
 647 | 
 648 |             for (var url of findHardcodedURLs(auraBody)){
 649 |               let escapedURL = url.replace(/(? 0) {
 723 |             incrementValue(componentProperties,`AuthProvider.${authProviderName}.NamedCredential.${ncName}`);
 724 |           }
 725 |           if (fs.existsSync(ncFile)) {
 726 |             let nc = fs.readFileSync(ncFile, 'utf8');
 727 |             for (var url of findHardcodedURLs(nc)){
 728 |               let escapedURL = url.replace(/(? 0) {
 747 |             setValue(componentProperties,`AuthProvider.${apName}.packagedSecret`,1);
 748 |           }
 749 |           typeInv['ProviderTypes'] = apTypes;
 750 |  
 751 |         }
 752 |       break;
 753 |     }
 754 | 
 755 |     inventory[metadataType] = typeInv;
 756 |   }
 757 |  
 758 |   //Check Person Accounts
 759 |   let pafile = `${sourceDir}/objects/PersonAccount.object`;
 760 |   if (fs.existsSync(pafile)) {
 761 |     setValue(dependencies,'features.PersonAccount',1);
 762 |   }
 763 | 
 764 |   inventory['apiVersions'] = apiVersions;
 765 |   inventory['componentProperties'] = componentProperties;
 766 |   inventory['dependencies'] = dependencies;
 767 |   inventory['language'] = language;
 768 |   return inventory;
 769 | }
 770 | 
 771 | function getMembers(mdTypeDef, sourceDir) {
 772 |   let retVal = mdTypeDef['members'];
 773 |   if (mdTypeDef.name == 'CustomField') {
 774 |     retVal = getFieldMembersFromObjects(sourceDir);
 775 |   }
 776 |   else if (mdmap[mdTypeDef.name] != undefined) {
 777 |      if (mdmap[mdTypeDef.name]['folder'] != 'null' && mdmap[mdTypeDef.name]['extension'] != 'null') {
 778 |       retVal = getMembersFromFiles(`${sourceDir}/${mdmap[mdTypeDef.name]['folder']}`, mdmap[mdTypeDef.name]['extension']);
 779 |     }
 780 |   }
 781 |   return retVal;
 782 | }
 783 | 
 784 | function getFieldMembersFromObjects(sourceDir: String) {
 785 |   const members = [];
 786 | //Get Wildcard Fields from Object Definitions
 787 | const objectPath = `${sourceDir}/objects`;
 788 | if (fs.existsSync(objectPath)) {
 789 |   const objectDefs = fs.readdirSync(objectPath);
 790 |   objectDefs.forEach(element => {
 791 | 
 792 |     const [objectName, ext] = [element.substr(0, element.lastIndexOf('.')), element.substr(element.lastIndexOf('.') + 1, element.length)]
 793 |     if (ext === 'object') {
 794 |       const objectXml = `${objectPath}/${objectName}.object`;
 795 |      
 796 |       const objectJSON = parseXML(objectXml);
 797 |       
 798 |       if (objectJSON['CustomObject'] && objectJSON['CustomObject']['fields']) {
 799 |         for (var fieldDef of objectJSON['CustomObject']['fields']) {
 800 |           members.push(objectName + '.' + fieldDef['fullName']);
 801 |         }
 802 |       }
 803 | 
 804 |     }
 805 |   });
 806 | }
 807 |   return members;
 808 | }
 809 | 
 810 | 
 811 | function getMembersFromFiles(folder, extension) {
 812 |   const typePath = folder;
 813 |   const members = [];
 814 |   if (!fs.existsSync(typePath)) {
 815 |     return members;
 816 |   }
 817 |   const folderContents = fs.readdirSync(typePath);
 818 |   folderContents.forEach(element => {
 819 | 
 820 |     const [fileName, ext] = [element.substr(0, element.lastIndexOf('.')), element.substr(element.lastIndexOf('.') + 1, element.length)]
 821 |     if (ext === extension) {
 822 |       members.push(fileName);
 823 |     }
 824 |   });
 825 | 
 826 |   return members;
 827 | }
 828 | 
 829 | function findHardcodedURLs(textToScan: string) {
 830 |   const urlReg = /((?:na|eu|ap|cs|gs)\d{1,3}\.salesforce\.com)/ig;
 831 | 
 832 |   return getMatches(textToScan, urlReg);
 833 | }
 834 | 
 835 | function extractObjectsApex(apexBody: string)  {
 836 |   const findObjectsReg = /(?:(?[a-zA-Z](?:[a-z]|[A-Z]|[0-9]|_(?!_)){0,14})__)?(?(?c|mdt|e|x|b|pc|pr|r|xo|latitude__s|longitude__s|history|ka|kav|feed|share))/g;
 837 |    
 838 |   let objectsFound = [];
 839 |   let objectFound;
 840 |   while (objectFound = findObjectsReg.exec(apexBody)) {
 841 |     objectsFound.push(getNameSpaceAndType(objectFound[0]));
 842 |   }  
 843 |   return objectsFound;      
 844 |  }
 845 | 
 846 |  function addObjectDependencies(dependencies: Object, objects: Object[]) {
 847 |  
 848 |     for (const o of objects) {
 849 |       if (o['namespace'] !== null) {
 850 |         setValue(dependencies,`namespaces.${o['namespace']}`,1);
 851 |       }
 852 |       setValue(dependencies,`components.${o['fullName']}`,o);
 853 |     }
 854 |  }
 855 | 
 856 | 
 857 | 
 858 | function stripApexComments(apexBody : string) {
 859 |   const commentReg = /\/\*[\s\S]*?\*\/|((? 2 && (breakdown[breakdown.length-2] == 'latitude' || breakdown[breakdown.length-2] == 'longitude' )) {
 916 |         breakdown.pop();
 917 |         breakdown[breakdown.length -1] = breakdown[breakdown.length -1] + '__s';
 918 |       
 919 |     }
 920 | 
 921 |     //we have an extension and possibly a namespace
 922 |     retVal.extension = breakdown[breakdown.length -1];
 923 |     
 924 |     if (breakdown.length == 2) {
 925 |       //non namespaced custom object
 926 |       retVal.name = breakdown[0];
 927 |     }
 928 |     else if (breakdown.length == 3) {
 929 |       retVal.name = breakdown[1];
 930 |       retVal.namespace = breakdown[0];
 931 |     }
 932 |     else {
 933 |       //We probably shouldn't end up here.
 934 |       retVal.name = fullComponentName;
 935 |     }
 936 |     switch (retVal.extension) {
 937 |       case 'c':
 938 |         retVal.type = 'Custom';
 939 |         break;
 940 |       case 'r':
 941 |         retVal.type = 'Relationship';
 942 |         break;
 943 |       case 'ka':
 944 |         retVal.type = 'Knowledge Article';
 945 |         break;
 946 |       case 'kav':
 947 |         retVal.type = 'Knowledge Article Version';
 948 |         break;
 949 |       case 'feed':
 950 |         retVal.type = 'Object Feed';
 951 |         break;
 952 |       case 'viewstat':
 953 |         retVal.type = 'Knowledge Article View Stat';
 954 |         break;
 955 |       case 'votestat':
 956 |         retVal.type = 'Knowledge Article Vote Stat';
 957 |         break;
 958 |       case 'datacategoryselection':
 959 |         retVal.type = 'Knowledge Article Data Category';
 960 |         break;
 961 |       case 'x':
 962 |         retVal.type = 'External Object';
 963 |         break;
 964 |       case 'xo':
 965 |         retVal.type = 'S2S Proxy Object';
 966 |         break;
 967 |       case 'mdt':
 968 |         retVal.type = 'Custom Metadata Type';
 969 |         break;
 970 |       case 'share':
 971 |         retVal.type = 'Custom Object Sharing';
 972 |         break;
 973 |       case 'tag':
 974 |         retVal.type = 'Tag';
 975 |         break;
 976 |       case 'history':
 977 |         retVal.type = 'Field History Tracking';
 978 |         break;
 979 |       case 'pc':
 980 |         retVal.type = 'Person Account';
 981 |         break;
 982 |       case 'pr':
 983 |         retVal.type = 'Person Account Relationship';
 984 |         break;
 985 |       case 'b':
 986 |         retVal.type = 'Big Object';
 987 |         break;
 988 |       case 'latitude__s':
 989 |         retVal.type = 'Geolocation Latitude Coordinate';
 990 |         break;
 991 |       case 'longitude__s':
 992 |         retVal.type = 'Geolocation Longitude Coordinate';
 993 |         break;
 994 |       case 'e':
 995 |         retVal.type = 'Platform Event';
 996 |         break;
 997 |       case 'p':
 998 |         retVal.type = 'Custom Person Object';
 999 |         break;
1000 |       case 'changeevent':
1001 |         retVal.type = 'Change Data Capture';
1002 |         break;
1003 |       default:
1004 |         retVal.type = 'Unknown';
1005 |     }
1006 | 
1007 |   }
1008 |   return retVal;
1009 | }
1010 | 
1011 | export function parseXML(xmlfile, dieOnError = false) {
1012 |   const parser = new xml2js.Parser({
1013 |     attrkey: 'ATTR'
1014 |   });
1015 |   let json = [];
1016 |   let error = undefined;
1017 | 
1018 |   if (!fs.existsSync(xmlfile)) {
1019 |     let message = `Cannot find XML File: ${xmlfile}`;
1020 |     if (dieOnError == true) {
1021 |       throw new SfdxError(message, 'XMLNotFoundError');
1022 |     } else {
1023 |       return json;
1024 |     }
1025 |   }
1026 |   let xmlData = fs.readFileSync(xmlfile, 'utf8');
1027 |   parser.parseString(xmlData.substring(0, xmlData.length), function (err, result) {
1028 |     error = err;
1029 |     json = result || json;
1030 |   });
1031 | 
1032 |   if (error && dieOnError) {
1033 |     throw new SfdxError(`Error parsing ${xmlfile}: ${error}`, 'XMLParseError');
1034 |   }
1035 |   return json;
1036 | }
1037 | 
1038 | function isObject(o: any): boolean {
1039 |   return (o !== null && typeof o === 'object' && !Array.isArray(o));
1040 | }
1041 | 
1042 | 
1043 | 
1044 | function languageScanMetadataObject(mdObject: any, ignoreProperties: string[] = []): any {
1045 |   let result = [];
1046 |   let tmpResult;
1047 |   const textyKeysreg = /[d|D]escription|[l|L]abel|[h|H]elp|[t|T]ext|[n|N]ame|[v|V]alue/;
1048 |   if (isObject(mdObject)) {
1049 |     for (let [key, value] of Object.entries(mdObject)) {
1050 |       const isTexty = textyKeysreg.test(key);
1051 |       if (isObject(value)) {
1052 |         if (tmpResult = languageScanMetadataObject(value)) {
1053 |           result.push(...tmpResult);
1054 |         }
1055 |       }
1056 |       else if (Array.isArray(value)) {
1057 |         for (const k of value) {
1058 |           if (isObject(k)) {
1059 |             if (tmpResult = languageScanMetadataObject(k)) {
1060 |               result.push(...tmpResult);
1061 |             }
1062 |           }
1063 |           else {
1064 |             if (isTexty) {
1065 |               if (tmpResult = languageScan(k)) {
1066 |                 for (let r of tmpResult) {
1067 |                   r.context = `Property: ${key} = ${r.context}`;
1068 |                 }
1069 |                 result.push(...tmpResult);
1070 |               }
1071 |             }
1072 |           }
1073 |         }
1074 |       }
1075 |       else if (isTexty) {
1076 |         if (tmpResult = languageScan(mdObject[key])) {
1077 |           for (let r of tmpResult) {
1078 |             r.context = `Property: ${key} = ${r.context}`;
1079 |           }
1080 |           result.push(...tmpResult);
1081 |         }
1082 |       }
1083 | 
1084 |     }
1085 |   }
1086 |   if (result.length > 0) {
1087 |     return result;
1088 |   }
1089 |   else {
1090 |     return false;
1091 |   }
1092 | }
1093 | 
1094 | function languageScan(text: string, type : string = 'text') : any {
1095 |   //return immediately if text is empty
1096 |   if (!text) {
1097 |     return false;
1098 |   }
1099 |   let scanResult = {};
1100 |   //let config= alexConfig;
1101 | 
1102 |   switch (type) {
1103 |     case 'text':
1104 |       scanResult = alex.text(text, alexConfig);
1105 |     break;
1106 |     case 'html':
1107 |       scanResult = alex.html(text, alexConfig);
1108 |     break;
1109 |     case 'markdown':
1110 |       scanResult = alex(text, alexConfig);
1111 |     break;
1112 |   }
1113 |   if (scanResult['messages'].length > 0) {
1114 |     let retVal = [];
1115 |     let lines = text.split(/\r?\n/);
1116 |     for (const result of scanResult['messages']) {
1117 |       retVal.push({
1118 |         message: result.message,
1119 |         details: result.note,
1120 |         context: lines[result.line -1],
1121 |         ruleName: result.ruleId,
1122 |         line: result.line
1123 |       })
1124 |       
1125 |     }
1126 |     return retVal;
1127 |   }
1128 |   else {
1129 |     return false;
1130 |   }
1131 | } 
1132 | 
1133 | 
1134 | 
1135 | 
1136 | 
1137 | 
1138 | 


--------------------------------------------------------------------------------
/src/common/mdmap.ts:
--------------------------------------------------------------------------------
   1 | /*
   2 |  * Copyright (c) 2018, salesforce.com, inc.
   3 |  * All rights reserved.
   4 |  * SPDX-License-Identifier: BSD-3-Clause
   5 |  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
   6 |  */
   7 | 
   8 |  const mdmap = {
   9 |   ActionLinkGroupTemplate: {
  10 | 		metadataType: 'ActionLinkGroupTemplate',
  11 | 		enumName: 'ACTIONLINK_GROUP_TEMPLATE',
  12 | 		folder: 'actionLinkGroupTemplates',
  13 | 		extension: 'actionLinkGroupTemplate'
  14 | 	},
  15 | 	ActionsSettings: {
  16 | 		metadataType: 'ActionsSettings',
  17 | 		enumName: 'ACTIONS_SETTINGS',
  18 | 		folder: 'settings',
  19 | 		extension: 'settings'
  20 | 	},
  21 | 	ArchiveSettings: {
  22 | 		metadataType: 'ArchiveSettings',
  23 | 		enumName: 'ARCHIVE_SETTINGS',
  24 | 		folder: 'settings',
  25 | 		extension: 'settings'
  26 | 	},
  27 | 	AnalyticSnapshot: {
  28 | 		metadataType: 'AnalyticSnapshot',
  29 | 		enumName: 'ANALYTICS_SNAPSHOT',
  30 | 		folder: 'analyticSnapshots',
  31 | 		extension: 'snapshot'
  32 | 	},
  33 | 	AnalyticsSettings: {
  34 | 		metadataType: 'AnalyticsSettings',
  35 | 		enumName: 'ANALYTICS_SETTINGS',
  36 | 		folder: 'settings',
  37 | 		extension: 'settings'
  38 | 	},
  39 | 	ApexClass: {
  40 | 		metadataType: 'ApexClass',
  41 | 		enumName: 'APEX_CLASS',
  42 | 		folder: 'classes',
  43 | 		extension: 'cls'
  44 | 	},
  45 | 	ApexComponent: {
  46 | 		metadataType: 'ApexComponent',
  47 | 		enumName: 'APEX_COMPONENT',
  48 | 		folder: 'components',
  49 | 		extension: 'component'
  50 | 	},
  51 | 	ApexPage: {
  52 | 		metadataType: 'ApexPage',
  53 | 		enumName: 'APEX_PAGE',
  54 | 		folder: 'pages',
  55 | 		extension: 'page'
  56 | 	},
  57 | 	ApexTrigger: {
  58 | 		metadataType: 'ApexTrigger',
  59 | 		enumName: 'APEX_TRIGGER',
  60 | 		folder: 'triggers',
  61 | 		extension: 'trigger'
  62 | 	},
  63 | 	ApexTestSuite: {
  64 | 		metadataType: 'ApexTestSuite',
  65 | 		enumName: 'APEX_TEST_SUITE',
  66 | 		folder: 'testSuites',
  67 | 		extension: 'testSuite'
  68 | 	},
  69 | 	AssignmentRules: {
  70 | 		metadataType: 'AssignmentRules',
  71 | 		enumName: 'ASSIGNMENT_RULES',
  72 | 		folder: 'assignmentRules',
  73 | 		extension: 'assignmentRules'
  74 | 	},
  75 | 	AssignmentRule: {
  76 | 		metadataType: 'AssignmentRule',
  77 | 		enumName: 'ASSIGNMENT_RULE',
  78 | 		folder: 'ASSIGNMENT_RULES',
  79 | 		extension: ''
  80 | 	},
  81 | 	AutoResponseRules: {
  82 | 		metadataType: 'AutoResponseRules',
  83 | 		enumName: 'AUTO_RESPONSE_RULES',
  84 | 		folder: 'autoResponseRules',
  85 | 		extension: 'autoResponseRules'
  86 | 	},
  87 | 	AutoResponseRule: {
  88 | 		metadataType: 'AutoResponseRule',
  89 | 		enumName: 'AUTO_RESPONSE_RULE',
  90 | 		folder: 'AUTO_RESPONSE_RULES',
  91 | 		extension: ''
  92 | 	},
  93 | 	Audience: {
  94 | 		metadataType: 'Audience',
  95 | 		enumName: 'AUDIENCE',
  96 | 		folder: 'audience',
  97 | 		extension: 'audience'
  98 | 	},
  99 | 	AuraDefinitionBundle: {
 100 | 		metadataType: 'AuraDefinitionBundle',
 101 | 		enumName: 'AURA_DEFINITION_BUNDLE',
 102 | 		folder: 'aura',
 103 | 		extension: 'null'
 104 | 	},
 105 | 	EntitlementProcess: {
 106 | 		metadataType: 'EntitlementProcess',
 107 | 		enumName: 'ENTITLEMENT_PROCESS',
 108 | 		folder: 'entitlementProcesses',
 109 | 		extension: 'entitlementProcess'
 110 | 	},
 111 | 	MilestoneType: {
 112 | 		metadataType: 'MilestoneType',
 113 | 		enumName: 'MILESTONE_TYPE',
 114 | 		folder: 'milestoneTypes',
 115 | 		extension: 'milestoneType'
 116 | 	},
 117 | 	CallCenter: {
 118 | 		metadataType: 'CallCenter',
 119 | 		enumName: 'CALL_CENTER',
 120 | 		folder: 'callCenters',
 121 | 		extension: 'callCenter'
 122 | 	},
 123 | 	Community: {
 124 | 		metadataType: 'Community',
 125 | 		enumName: 'COMMUNITY',
 126 | 		folder: 'communities',
 127 | 		extension: 'community'
 128 | 	},
 129 | 	CorsWhitelistOrigin: {
 130 | 		metadataType: 'CorsWhitelistOrigin',
 131 | 		enumName: 'CORS_WHITELIST_ORIGIN',
 132 | 		folder: 'corsWhitelistOrigins',
 133 | 		extension: 'corsWhitelistOrigin'
 134 | 	},
 135 | 	CustomApplication: {
 136 | 		metadataType: 'CustomApplication',
 137 | 		enumName: 'CUSTOM_APPLICATION',
 138 | 		folder: 'applications',
 139 | 		extension: 'app'
 140 | 	},
 141 | 	CustomDataType: {
 142 | 		metadataType: 'CustomDataType',
 143 | 		enumName: 'CUSTOM_DATA_TYPE',
 144 | 		folder: 'datatypes',
 145 | 		extension: 'datatype'
 146 | 	},
 147 | 	CustomHelpMenuSection: {
 148 | 		metadataType: 'CustomHelpMenuSection',
 149 | 		enumName: 'CUSTOM_HELP_MENU_SECTION',
 150 | 		folder: 'customHelpMenuSections',
 151 | 		extension: 'customHelpMenuSection'
 152 | 	},
 153 | 	CustomLabels: {
 154 | 		metadataType: 'CustomLabels',
 155 | 		enumName: 'CUSTOM_LABELS',
 156 | 		folder: 'labels',
 157 | 		extension: 'labels'
 158 | 	},
 159 | 	CustomObject: {
 160 | 		metadataType: 'CustomObject',
 161 | 		enumName: 'CUSTOM_OBJECT',
 162 | 		folder: 'objects',
 163 | 		extension: 'object'
 164 | 	},
 165 | 	CustomObjectTranslation: {
 166 | 		metadataType: 'CustomObjectTranslation',
 167 | 		enumName: 'CUSTOM_OBJECT_TRANSLATION',
 168 | 		folder: 'objectTranslations',
 169 | 		extension: 'objectTranslation'
 170 | 	},
 171 | 	CustomPageWebLink: {
 172 | 		metadataType: 'CustomPageWebLink',
 173 | 		enumName: 'CUSTOM_PAGE_WEBLINK',
 174 | 		folder: 'weblinks',
 175 | 		extension: 'weblink'
 176 | 	},
 177 | 	CustomSite: {
 178 | 		metadataType: 'CustomSite',
 179 | 		enumName: 'CUSTOM_SITE',
 180 | 		folder: 'sites',
 181 | 		extension: 'site'
 182 | 	},
 183 | 	CustomTab: {
 184 | 		metadataType: 'CustomTab',
 185 | 		enumName: 'CUSTOM_TAB',
 186 | 		folder: 'tabs',
 187 | 		extension: 'tab'
 188 | 	},
 189 | 	CustomMetadata: {
 190 | 		metadataType: 'CustomMetadata',
 191 | 		enumName: 'CUSTOM_METADATA',
 192 | 		folder: 'customMetadata',
 193 | 		extension: 'md'
 194 | 	},
 195 | 	Dashboard: {
 196 | 		metadataType: 'Dashboard',
 197 | 		enumName: 'DASHBOARD',
 198 | 		folder: 'dashboards',
 199 | 		extension: 'dashboard'
 200 | 	},
 201 | 	DashboardFolder: {
 202 | 		metadataType: 'DashboardFolder',
 203 | 		enumName: 'DASHBOARD_FOLDER',
 204 | 		folder: 'dashboards',
 205 | 		extension: 'null'
 206 | 	},
 207 | 	DataCategoryGroup: {
 208 | 		metadataType: 'DataCategoryGroup',
 209 | 		enumName: 'DATA_CATEGORY_GROUP',
 210 | 		folder: 'datacategorygroups',
 211 | 		extension: 'datacategorygroup'
 212 | 	},
 213 | 	DataPipeline: {
 214 | 		metadataType: 'DataPipeline',
 215 | 		enumName: 'DATA_PIPELINE',
 216 | 		folder: 'dataPipelines',
 217 | 		extension: 'dataPipeline'
 218 | 	},
 219 | 	DiscoverySettings: {
 220 | 		metadataType: 'DiscoverySettings',
 221 | 		enumName: 'DISCOVERY_SETTINGS',
 222 | 		folder: 'settings',
 223 | 		extension: 'settings'
 224 | 	},
 225 | 	Document: {
 226 | 		metadataType: 'Document',
 227 | 		enumName: 'DOCUMENT',
 228 | 		folder: 'documents',
 229 | 		extension: 'null'
 230 | 	},
 231 | 	DocumentFolder: {
 232 | 		metadataType: 'DocumentFolder',
 233 | 		enumName: 'DOCUMENT_FOLDER',
 234 | 		folder: 'documents',
 235 | 		extension: 'null'
 236 | 	},
 237 | 	DocumentType: {
 238 | 		metadataType: 'DocumentType',
 239 | 		enumName: 'DOCUMENT_TYPE',
 240 | 		folder: 'documenttypes',
 241 | 		extension: 'documenttype'
 242 | 	},
 243 | 	DuplicateRule: {
 244 | 		metadataType: 'DuplicateRule',
 245 | 		enumName: 'DUPLICATE_RULE',
 246 | 		folder: 'duplicateRules',
 247 | 		extension: 'duplicateRule'
 248 | 	},
 249 | 	EmailFolder: {
 250 | 		metadataType: 'EmailFolder',
 251 | 		enumName: 'EMAIL_FOLDER',
 252 | 		folder: 'email',
 253 | 		extension: 'null'
 254 | 	},
 255 | 	EmailServicesFunction: {
 256 | 		metadataType: 'EmailServicesFunction',
 257 | 		enumName: 'EMAIL_SERVICES_FUNCTION',
 258 | 		folder: 'emailservices',
 259 | 		extension: 'xml'
 260 | 	},
 261 | 	EmailTemplate: {
 262 | 		metadataType: 'EmailTemplate',
 263 | 		enumName: 'EMAIL_TEMPLATE',
 264 | 		folder: 'email',
 265 | 		extension: 'email'
 266 | 	},
 267 | 	EmailTemplateSettings: {
 268 | 		metadataType: 'EmailTemplateSettings',
 269 | 		enumName: 'EMAIL_TEMPLATE_SETTINGS',
 270 | 		folder: 'settings',
 271 | 		extension: 'settings'
 272 | 	},
 273 | 	EmbeddedServiceConfig: {
 274 | 		metadataType: 'EmbeddedServiceConfig',
 275 | 		enumName: 'EMBEDDED_SERVICE_FOR_WEB',
 276 | 		folder: 'EmbeddedServiceConfig',
 277 | 		extension: 'EmbeddedServiceConfig'
 278 | 	},
 279 | 	EmbeddedServiceLiveAgent: {
 280 | 		metadataType: 'EmbeddedServiceLiveAgent',
 281 | 		enumName: 'EMBEDDED_SERVICE_LIVE_AGENT',
 282 | 		folder: 'EmbeddedServiceLiveAgent',
 283 | 		extension: 'EmbeddedServiceLiveAgent'
 284 | 	},
 285 | 	EmbeddedServiceFieldService: {
 286 | 		metadataType: 'EmbeddedServiceFieldService',
 287 | 		enumName: 'EMBEDDED_SERVICE_FIELD_SERVICE',
 288 | 		folder: 'EmbeddedServiceFieldService',
 289 | 		extension: 'EmbeddedServiceFieldService'
 290 | 	},
 291 | 	EmbeddedServiceFlowConfig: {
 292 | 		metadataType: 'EmbeddedServiceFlowConfig',
 293 | 		enumName: 'EMBEDDED_SERVICE_FLOW_CONFIG',
 294 | 		folder: 'EmbeddedServiceFlowConfig',
 295 | 		extension: 'EmbeddedServiceFlowConfig'
 296 | 	},
 297 | 	EmbeddedServiceBranding: {
 298 | 		metadataType: 'EmbeddedServiceBranding',
 299 | 		enumName: 'EMBEDDED_SERVICE_BRANDING',
 300 | 		folder: 'EmbeddedServiceBranding',
 301 | 		extension: 'EmbeddedServiceBranding'
 302 | 	},
 303 | 	EmbeddedServiceMenuSettings: {
 304 | 		metadataType: 'EmbeddedServiceMenuSettings',
 305 | 		enumName: 'EMBEDDED_SERVICE_MENU_SETTINGS',
 306 | 		folder: 'EmbeddedServiceMenuSettings',
 307 | 		extension: 'EmbeddedServiceMenuSettings'
 308 | 	},
 309 | 	EnhancedNotesSettings: {
 310 | 		metadataType: 'EnhancedNotesSettings',
 311 | 		enumName: 'ENHANCED_NOTES_SETTINGS',
 312 | 		folder: 'settings',
 313 | 		extension: 'settings'
 314 | 	},
 315 | 	ExternalDataSource: {
 316 | 		metadataType: 'ExternalDataSource',
 317 | 		enumName: 'EXTERNAL_DATA_SOURCE',
 318 | 		folder: 'dataSources',
 319 | 		extension: 'dataSource'
 320 | 	},
 321 | 	EscalationRules: {
 322 | 		metadataType: 'EscalationRules',
 323 | 		enumName: 'ESCALATION_RULES',
 324 | 		folder: 'escalationRules',
 325 | 		extension: 'escalationRules'
 326 | 	},
 327 | 	EscalationRule: {
 328 | 		metadataType: 'EscalationRule',
 329 | 		enumName: 'ESCALATION_RULE',
 330 | 		folder: 'ESCALATION_RULES',
 331 | 		extension: ''
 332 | 	},
 333 | 	EssentialsSettings: {
 334 | 		metadataType: 'EssentialsSettings',
 335 | 		enumName: 'ESSENTIALS_SETTINGS',
 336 | 		folder: 'settings',
 337 | 		extension: 'settings'
 338 | 	},
 339 | 	TrialOrgSettings: {
 340 | 		metadataType: 'TrialOrgSettings',
 341 | 		enumName: 'TRIAL_ORG_SETTINGS',
 342 | 		folder: 'settings',
 343 | 		extension: 'settings'
 344 | 	},
 345 | 	EventSettings: {
 346 | 		metadataType: 'EventSettings',
 347 | 		enumName: 'EVENT_SETTINGS',
 348 | 		folder: 'settings',
 349 | 		extension: 'settings'
 350 | 	},
 351 | 	CustomFeedFilter: {
 352 | 		metadataType: 'CustomFeedFilter',
 353 | 		enumName: 'CUSTOM_FEED_FILTER',
 354 | 		folder: 'feedFilters',
 355 | 		extension: 'feedFilter'
 356 | 	},
 357 | 	FilesConnectSettings: {
 358 | 		metadataType: 'FilesConnectSettings',
 359 | 		enumName: 'FILES_CONNECT_SETTINGS',
 360 | 		folder: 'settings',
 361 | 		extension: 'settings'
 362 | 	},
 363 | 	FlexiPage: {
 364 | 		metadataType: 'FlexiPage',
 365 | 		enumName: 'FLEXI_PAGE',
 366 | 		folder: 'flexipages',
 367 | 		extension: 'flexipage'
 368 | 	},
 369 | 	Flow: {
 370 | 		metadataType: 'Flow',
 371 | 		enumName: 'FLOW',
 372 | 		folder: 'flows',
 373 | 		extension: 'flow'
 374 | 	},
 375 | 	FlowDefinition: {
 376 | 		metadataType: 'FlowDefinition',
 377 | 		enumName: 'FLOW_DEFINITION',
 378 | 		folder: 'flowDefinitions',
 379 | 		extension: 'flowDefinition'
 380 | 	},
 381 | 	FlowSettings: {
 382 | 		metadataType: 'FlowSettings',
 383 | 		enumName: 'FLOW_SETTINGS',
 384 | 		folder: 'settings',
 385 | 		extension: 'settings'
 386 | 	},
 387 | 	FlowCategory: {
 388 | 		metadataType: 'FlowCategory',
 389 | 		enumName: 'FLOW_CATEGORY',
 390 | 		folder: 'flowCategories',
 391 | 		extension: 'flowCategory'
 392 | 	},
 393 | 	Form: {
 394 | 		metadataType: 'Form',
 395 | 		enumName: 'FORM',
 396 | 		folder: 'forms',
 397 | 		extension: 'form'
 398 | 	},
 399 | 	FormSection: {
 400 | 		metadataType: 'FormSection',
 401 | 		enumName: 'FORM_SECTION',
 402 | 		folder: 'FORM',
 403 | 		extension: ''
 404 | 	},
 405 | 	HomePageComponent: {
 406 | 		metadataType: 'HomePageComponent',
 407 | 		enumName: 'HOME_PAGE_COMPONENT',
 408 | 		folder: 'homePageComponents',
 409 | 		extension: 'homePageComponent'
 410 | 	},
 411 | 	HomePageLayout: {
 412 | 		metadataType: 'HomePageLayout',
 413 | 		enumName: 'HOME_PAGE_LAYOUT',
 414 | 		folder: 'homePageLayouts',
 415 | 		extension: 'homePageLayout'
 416 | 	},
 417 | 	HighVelocitySalesSettings: {
 418 | 		metadataType: 'HighVelocitySalesSettings',
 419 | 		enumName: 'HIGH_VELOCITY_SALES_SETTINGS',
 420 | 		folder: 'settings',
 421 | 		extension: 'settings'
 422 | 	},
 423 | 	ConversationalIntelligenceSettings: {
 424 | 		metadataType: 'ConversationalIntelligenceSettings',
 425 | 		enumName: 'CONVERSATIONAL_INTELLIGENCE_SETTINGS',
 426 | 		folder: 'settings',
 427 | 		extension: 'settings'
 428 | 	},
 429 | 	InboundNetworkConnection: {
 430 | 		metadataType: 'InboundNetworkConnection',
 431 | 		enumName: 'INBOUND_NETWORK_CONNECTION',
 432 | 		folder: 'inboundNetworkConnections',
 433 | 		extension: 'inboundNetworkConnection'
 434 | 	},
 435 | 	IsvHammerSettings: {
 436 | 		metadataType: 'IsvHammerSettings',
 437 | 		enumName: 'ISV_HAMMER_SETTINGS',
 438 | 		folder: 'settings',
 439 | 		extension: 'settings'
 440 | 	},
 441 | 	IdeasSettings: {
 442 | 		metadataType: 'IdeasSettings',
 443 | 		enumName: 'IDEAS_SETTINGS',
 444 | 		folder: 'settings',
 445 | 		extension: 'settings'
 446 | 	},
 447 | 	IframeWhiteListUrlSettings: {
 448 | 		metadataType: 'IframeWhiteListUrlSettings',
 449 | 		enumName: 'IFRAME_WHITELIST_URL_SETTINGS',
 450 | 		folder: 'iframeWhiteListUrlSettings',
 451 | 		extension: 'iframeWhiteListUrlSettings'
 452 | 	},
 453 | 	PortalsSettings: {
 454 | 		metadataType: 'PortalsSettings',
 455 | 		enumName: 'PORTALS_SETTINGS',
 456 | 		folder: 'settings',
 457 | 		extension: 'settings'
 458 | 	},
 459 | 	InvocableActionSettings: {
 460 | 		metadataType: 'InvocableActionSettings',
 461 | 		enumName: 'INVOCABLE_ACTION_SETTINGS',
 462 | 		folder: 'settings',
 463 | 		extension: 'settings'
 464 | 	},
 465 | 	ChatterAnswersSettings: {
 466 | 		metadataType: 'ChatterAnswersSettings',
 467 | 		enumName: 'CHATTER_ANSWERS_SETTINGS',
 468 | 		folder: 'settings',
 469 | 		extension: 'settings'
 470 | 	},
 471 | 	ChatterExtension: {
 472 | 		metadataType: 'ChatterExtension',
 473 | 		enumName: 'CHATTER_EXTENSION',
 474 | 		folder: 'ChatterExtensions',
 475 | 		extension: 'ChatterExtension'
 476 | 	},
 477 | 	ChatterSettings: {
 478 | 		metadataType: 'ChatterSettings',
 479 | 		enumName: 'CHATTER_SETTINGS',
 480 | 		folder: 'settings',
 481 | 		extension: 'settings'
 482 | 	},
 483 | 	EntitlementSettings: {
 484 | 		metadataType: 'EntitlementSettings',
 485 | 		enumName: 'ENTITLEMENT_SETTINGS',
 486 | 		folder: 'settings',
 487 | 		extension: 'settings'
 488 | 	},
 489 | 	Layout: {
 490 | 		metadataType: 'Layout',
 491 | 		enumName: 'LAYOUT',
 492 | 		folder: 'layouts',
 493 | 		extension: 'layout'
 494 | 	},
 495 | 	Letterhead: {
 496 | 		metadataType: 'Letterhead',
 497 | 		enumName: 'LETTERHEAD',
 498 | 		folder: 'letterhead',
 499 | 		extension: 'letter'
 500 | 	},
 501 | 	LeadConvertSettings: {
 502 | 		metadataType: 'LeadConvertSettings',
 503 | 		enumName: 'LEAD_CONVERT_SETTINGS',
 504 | 		folder: 'LeadConvertSettings',
 505 | 		extension: 'LeadConvertSetting'
 506 | 	},
 507 | 	LeadConfigSettings: {
 508 | 		metadataType: 'LeadConfigSettings',
 509 | 		enumName: 'LEAD_CONFIG_SETTINGS',
 510 | 		folder: 'settings',
 511 | 		extension: 'settings'
 512 | 	},
 513 | 	LightningBolt: {
 514 | 		metadataType: 'LightningBolt',
 515 | 		enumName: 'LIGHTNING_BOLT',
 516 | 		folder: 'lightningBolts',
 517 | 		extension: 'lightningBolt'
 518 | 	},
 519 | 	LightningExperienceTheme: {
 520 | 		metadataType: 'LightningExperienceTheme',
 521 | 		enumName: 'LIGHTNING_EXPERIENCE_THEME',
 522 | 		folder: 'lightningExperienceThemes',
 523 | 		extension: 'lightningExperienceTheme'
 524 | 	},
 525 | 	LightningOnboardingConfig: {
 526 | 		metadataType: 'LightningOnboardingConfig',
 527 | 		enumName: 'LIGHTNING_ONBOARDING_CONFIG',
 528 | 		folder: 'lightningOnboardingConfigs',
 529 | 		extension: 'lightningOnboardingConfig'
 530 | 	},
 531 | 	LightningComponentBundle: {
 532 | 		metadataType: 'LightningComponentBundle',
 533 | 		enumName: 'LIGHTNING_COMPONENT_BUNDLE',
 534 | 		folder: 'lwc',
 535 | 		extension: 'null'
 536 | 	},
 537 | 	LiveAgentSettings: {
 538 | 		metadataType: 'LiveAgentSettings',
 539 | 		enumName: 'LIVE_AGENT_SETTINGS',
 540 | 		folder: 'settings',
 541 | 		extension: 'settings'
 542 | 	},
 543 | 	LiveChatAgentConfig: {
 544 | 		metadataType: 'LiveChatAgentConfig',
 545 | 		enumName: 'LIVE_CHAT_AGENT_CONFIG',
 546 | 		folder: 'liveChatAgentConfigs',
 547 | 		extension: 'liveChatAgentConfig'
 548 | 	},
 549 | 	LiveChatButton: {
 550 | 		metadataType: 'LiveChatButton',
 551 | 		enumName: 'LIVE_CHAT_BUTTON',
 552 | 		folder: 'liveChatButtons',
 553 | 		extension: 'liveChatButton'
 554 | 	},
 555 | 	LiveChatDeployment: {
 556 | 		metadataType: 'LiveChatDeployment',
 557 | 		enumName: 'LIVE_CHAT_DEPLOYMENT',
 558 | 		folder: 'liveChatDeployments',
 559 | 		extension: 'liveChatDeployment'
 560 | 	},
 561 | 	LiveChatSensitiveDataRule: {
 562 | 		metadataType: 'LiveChatSensitiveDataRule',
 563 | 		enumName: 'LIVE_CHAT_SENSITIVE_DATA_RULE',
 564 | 		folder: 'liveChatSensitiveDataRule',
 565 | 		extension: 'liveChatSensitiveDataRule'
 566 | 	},
 567 | 	MarketAudienceDefinition: {
 568 | 		metadataType: 'MarketAudienceDefinition',
 569 | 		enumName: 'MARKET_AUDIENCE_DEFINITION',
 570 | 		folder: 'marketAudienceDefinitions',
 571 | 		extension: 'marketAudienceDefinition'
 572 | 	},
 573 | 	MatchingRules: {
 574 | 		metadataType: 'MatchingRules',
 575 | 		enumName: 'MATCHING_RULES',
 576 | 		folder: 'matchingRules',
 577 | 		extension: 'matchingRule'
 578 | 	},
 579 | 	MatchingRule: {
 580 | 		metadataType: 'MatchingRule',
 581 | 		enumName: 'MATCHING_RULE',
 582 | 		folder: 'MATCHING_RULES',
 583 | 		extension: ''
 584 | 	},
 585 | 	MobileSettings: {
 586 | 		metadataType: 'MobileSettings',
 587 | 		enumName: 'MOBILE_SETTINGS',
 588 | 		folder: 'settings',
 589 | 		extension: 'settings'
 590 | 	},
 591 | 	MyDomainSettings: {
 592 | 		metadataType: 'MyDomainSettings',
 593 | 		enumName: 'MY_DOMAIN_SETTINGS',
 594 | 		folder: 'settings',
 595 | 		extension: 'settings'
 596 | 	},
 597 | 	CommunitiesSettings: {
 598 | 		metadataType: 'CommunitiesSettings',
 599 | 		enumName: 'COMMUNITIES_SETTINGS',
 600 | 		folder: 'settings',
 601 | 		extension: 'settings'
 602 | 	},
 603 | 	ConnectedAppSettings: {
 604 | 		metadataType: 'ConnectedAppSettings',
 605 | 		enumName: 'CONNECTED_APP_SETTINGS',
 606 | 		folder: 'settings',
 607 | 		extension: 'settings'
 608 | 	},
 609 | 	ContentSettings: {
 610 | 		metadataType: 'ContentSettings',
 611 | 		enumName: 'CONTENT_SETTINGS',
 612 | 		folder: 'settings',
 613 | 		extension: 'settings'
 614 | 	},
 615 | 	EmailAdministrationSettings: {
 616 | 		metadataType: 'EmailAdministrationSettings',
 617 | 		enumName: 'EMAIL_ADMINISTRATION_SETTINGSS',
 618 | 		folder: 'settings',
 619 | 		extension: 'settings'
 620 | 	},
 621 | 	NamedCredential: {
 622 | 		metadataType: 'NamedCredential',
 623 | 		enumName: 'NAMED_CREDENTIAL',
 624 | 		folder: 'namedCredentials',
 625 | 		extension: 'namedCredential'
 626 | 	},
 627 | 	OmniChannelSettings: {
 628 | 		metadataType: 'OmniChannelSettings',
 629 | 		enumName: 'OMNI_CHANNEL_SETTINGS',
 630 | 		folder: 'settings',
 631 | 		extension: 'settings'
 632 | 	},
 633 | 	OutboundNetworkConnection: {
 634 | 		metadataType: 'OutboundNetworkConnection',
 635 | 		enumName: 'OUTBOUND_NETWORK_CONNECTION',
 636 | 		folder: 'outboundNetworkConnections',
 637 | 		extension: 'outboundNetworkConnection'
 638 | 	},
 639 | 	PermissionSet: {
 640 | 		metadataType: 'PermissionSet',
 641 | 		enumName: 'PERMISSION_SET',
 642 | 		folder: 'permissionsets',
 643 | 		extension: 'permissionset'
 644 | 	},
 645 | 	PermissionSetGroup: {
 646 | 		metadataType: 'PermissionSetGroup',
 647 | 		enumName: 'PERMISSION_SET_GROUP',
 648 | 		folder: 'permissionsetgroups',
 649 | 		extension: 'permissionsetgroup'
 650 | 	},
 651 | 	MutingPermissionSet: {
 652 | 		metadataType: 'MutingPermissionSet',
 653 | 		enumName: 'MUTING_PERMISSION_SET',
 654 | 		folder: 'mutingpermissionsets',
 655 | 		extension: 'mutingpermissionset'
 656 | 	},
 657 | 	Portal: {
 658 | 		metadataType: 'Portal',
 659 | 		enumName: 'PORTAL',
 660 | 		folder: 'portals',
 661 | 		extension: 'portal'
 662 | 	},
 663 | 	PredictionBuilderSettings: {
 664 | 		metadataType: 'PredictionBuilderSettings',
 665 | 		enumName: 'PREDICTION_BUILDER_SETTINGS',
 666 | 		folder: 'settings',
 667 | 		extension: 'settings'
 668 | 	},
 669 | 	Profile: {
 670 | 		metadataType: 'Profile',
 671 | 		enumName: 'PROFILE',
 672 | 		folder: 'profiles',
 673 | 		extension: 'profile'
 674 | 	},
 675 | 	PicklistSettings: {
 676 | 		metadataType: 'PicklistSettings',
 677 | 		enumName: 'PICKLIST_SETTINGS',
 678 | 		folder: 'settings',
 679 | 		extension: 'settings'
 680 | 	},
 681 | 	PlatformEncryptionSettings: {
 682 | 		metadataType: 'PlatformEncryptionSettings',
 683 | 		enumName: 'PLATFORM_ENCRYPTION_SETTINGS',
 684 | 		folder: 'settings',
 685 | 		extension: 'settings'
 686 | 	},
 687 | 	EncryptionKeySettings: {
 688 | 		metadataType: 'EncryptionKeySettings',
 689 | 		enumName: 'ENCRYPTION__KEY_SETTINGS',
 690 | 		folder: 'settings',
 691 | 		extension: 'settings'
 692 | 	},
 693 | 	EmailIntegrationSettings: {
 694 | 		metadataType: 'EmailIntegrationSettings',
 695 | 		enumName: 'EMAIL_INTEGRATION_SETTINGS',
 696 | 		folder: 'settings',
 697 | 		extension: 'settings'
 698 | 	},
 699 | 	SocialProfileSettings: {
 700 | 		metadataType: 'SocialProfileSettings',
 701 | 		enumName: 'SOCIAL_PROFILE_SETTINGS',
 702 | 		folder: 'settings',
 703 | 		extension: 'settings'
 704 | 	},
 705 | 	UserManagementSettings: {
 706 | 		metadataType: 'UserManagementSettings',
 707 | 		enumName: 'USER_MANAGEMENT_SETTINGS',
 708 | 		folder: 'settings',
 709 | 		extension: 'settings'
 710 | 	},
 711 | 	DataDotComSettings: {
 712 | 		metadataType: 'DataDotComSettings',
 713 | 		enumName: 'DATADOTCOM_SETTINGS',
 714 | 		folder: 'settings',
 715 | 		extension: 'settings'
 716 | 	},
 717 | 	ProfilePasswordPolicy: {
 718 | 		metadataType: 'ProfilePasswordPolicy',
 719 | 		enumName: 'PROFILE_PASSWORD_POLICY',
 720 | 		folder: 'profilePasswordPolicies',
 721 | 		extension: 'profilePasswordPolicy'
 722 | 	},
 723 | 	ProfileSessionSetting: {
 724 | 		metadataType: 'ProfileSessionSetting',
 725 | 		enumName: 'PROFILE_SESSION_SETTING',
 726 | 		folder: 'profileSessionSettings',
 727 | 		extension: 'profileSessionSetting'
 728 | 	},
 729 | 	OauthCustomScope: {
 730 | 		metadataType: 'OauthCustomScope',
 731 | 		enumName: 'OAUTH_CUSTOM_SCOPE',
 732 | 		folder: 'oauthcustomscopes',
 733 | 		extension: 'oauthcustomscope'
 734 | 	},
 735 | 	OrgSettings: {
 736 | 		metadataType: 'OrgSettings',
 737 | 		enumName: 'ORG_SETTINGS',
 738 | 		folder: 'settings',
 739 | 		extension: 'settings'
 740 | 	},
 741 | 	UserInterfaceSettings: {
 742 | 		metadataType: 'UserInterfaceSettings',
 743 | 		enumName: 'USER_INTERFACE_SETTINGS',
 744 | 		folder: 'settings',
 745 | 		extension: 'settings'
 746 | 	},
 747 | 	RemoteSiteSetting: {
 748 | 		metadataType: 'RemoteSiteSetting',
 749 | 		enumName: 'REMOTE_SITE_SETTING',
 750 | 		folder: 'remoteSiteSettings',
 751 | 		extension: 'remoteSite'
 752 | 	},
 753 | 	CspTrustedSite: {
 754 | 		metadataType: 'CspTrustedSite',
 755 | 		enumName: 'CSPTRUSTEDSITE',
 756 | 		folder: 'cspTrustedSites',
 757 | 		extension: 'cspTrustedSite'
 758 | 	},
 759 | 	RedirectWhitelistUrl: {
 760 | 		metadataType: 'RedirectWhitelistUrl',
 761 | 		enumName: 'REDIRECT_WHITELIST_URL',
 762 | 		folder: 'redirectWhitelistUrls',
 763 | 		extension: 'redirectWhitelistUrl'
 764 | 	},
 765 | 	Report: {
 766 | 		metadataType: 'Report',
 767 | 		enumName: 'REPORT',
 768 | 		folder: 'reports',
 769 | 		extension: 'report'
 770 | 	},
 771 | 	ReportFolder: {
 772 | 		metadataType: 'ReportFolder',
 773 | 		enumName: 'REPORT_FOLDER',
 774 | 		folder: 'reports',
 775 | 		extension: 'null'
 776 | 	},
 777 | 	ReportType: {
 778 | 		metadataType: 'ReportType',
 779 | 		enumName: 'REPORT_TYPE',
 780 | 		folder: 'reportTypes',
 781 | 		extension: 'reportType'
 782 | 	},
 783 | 	VisualizationPlugin: {
 784 | 		metadataType: 'VisualizationPlugin',
 785 | 		enumName: 'VISUALIZATION_PLUGIN',
 786 | 		folder: 'visualizationPlugins',
 787 | 		extension: 'visualizationPlugin'
 788 | 	},
 789 | 	Role: {
 790 | 		metadataType: 'Role',
 791 | 		enumName: 'ROLE',
 792 | 		folder: 'roles',
 793 | 		extension: 'role'
 794 | 	},
 795 | 	SchemaSettings: {
 796 | 		metadataType: 'SchemaSettings',
 797 | 		enumName: 'SCHEMA_SETTINGS',
 798 | 		folder: 'settings',
 799 | 		extension: 'settings'
 800 | 	},
 801 | 	Scontrol: {
 802 | 		metadataType: 'Scontrol',
 803 | 		enumName: 'SCONTROL',
 804 | 		folder: 'scontrols',
 805 | 		extension: 'scf'
 806 | 	},
 807 | 	SearchSettings: {
 808 | 		metadataType: 'SearchSettings',
 809 | 		enumName: 'SEARCH_SETTINGS',
 810 | 		folder: 'settings',
 811 | 		extension: 'settings'
 812 | 	},
 813 | 	SecuritySettings: {
 814 | 		metadataType: 'SecuritySettings',
 815 | 		enumName: 'SECURITY_SETTINGS',
 816 | 		folder: 'settings',
 817 | 		extension: 'settings'
 818 | 	},
 819 | 	ServiceChannel: {
 820 | 		metadataType: 'ServiceChannel',
 821 | 		enumName: 'SERVICE_CHANNEL',
 822 | 		folder: 'serviceChannels',
 823 | 		extension: 'serviceChannel'
 824 | 	},
 825 | 	ServicePresenceStatus: {
 826 | 		metadataType: 'ServicePresenceStatus',
 827 | 		enumName: 'SERVICE_PRESENCE_STATUS',
 828 | 		folder: 'servicePresenceStatuses',
 829 | 		extension: 'servicePresenceStatus'
 830 | 	},
 831 | 	SharingSettings: {
 832 | 		metadataType: 'SharingSettings',
 833 | 		enumName: 'SHARING_SETTINGS',
 834 | 		folder: 'settings',
 835 | 		extension: 'settings'
 836 | 	},
 837 | 	Skill: {
 838 | 		metadataType: 'Skill',
 839 | 		enumName: 'SKILL',
 840 | 		folder: 'skills',
 841 | 		extension: 'skill'
 842 | 	},
 843 | 	SynonymDictionary: {
 844 | 		metadataType: 'SynonymDictionary',
 845 | 		enumName: 'SYNONYM_DICTIONARY',
 846 | 		folder: 'synonymDictionaries',
 847 | 		extension: 'synonymDictionary'
 848 | 	},
 849 | 	StaticResource: {
 850 | 		metadataType: 'StaticResource',
 851 | 		enumName: 'STATIC_RESOURCE',
 852 | 		folder: 'staticresources',
 853 | 		extension: 'resource'
 854 | 	},
 855 | 	ContentAsset: {
 856 | 		metadataType: 'ContentAsset',
 857 | 		enumName: 'CONTENT_ASSET',
 858 | 		folder: 'contentassets',
 859 | 		extension: 'asset'
 860 | 	},
 861 | 	Territory: {
 862 | 		metadataType: 'Territory',
 863 | 		enumName: 'TERRITORY',
 864 | 		folder: 'territories',
 865 | 		extension: 'territory'
 866 | 	},
 867 | 	Translations: {
 868 | 		metadataType: 'Translations',
 869 | 		enumName: 'TRANSLATIONS',
 870 | 		folder: 'translations',
 871 | 		extension: 'translation'
 872 | 	},
 873 | 	ApprovalProcess: {
 874 | 		metadataType: 'ApprovalProcess',
 875 | 		enumName: 'APPROVAL_PROCESS',
 876 | 		folder: 'approvalProcesses',
 877 | 		extension: 'approvalProcess'
 878 | 	},
 879 | 	PostTemplate: {
 880 | 		metadataType: 'PostTemplate',
 881 | 		enumName: 'POST_TEMPLATES',
 882 | 		folder: 'postTemplates',
 883 | 		extension: 'postTemplate'
 884 | 	},
 885 | 	WorkSkillRouting: {
 886 | 		metadataType: 'WorkSkillRouting',
 887 | 		enumName: 'WORK_SKILL_ROUTING',
 888 | 		folder: 'workSkillRoutings',
 889 | 		extension: 'workSkillRouting'
 890 | 	},
 891 | 	Workflow: {
 892 | 		metadataType: 'Workflow',
 893 | 		enumName: 'WORKFLOW',
 894 | 		folder: 'workflows',
 895 | 		extension: 'workflow'
 896 | 	},
 897 | 	UserLicenseDefinition: {
 898 | 		metadataType: 'UserLicenseDefinition',
 899 | 		enumName: 'USER_LICENSE_DEFN',
 900 | 		folder: 'userlicensedefinitions',
 901 | 		extension: 'userlicensedefinition'
 902 | 	},
 903 | 	CleanDataService: {
 904 | 		metadataType: 'CleanDataService',
 905 | 		enumName: 'CLEAN_DATASERVICE',
 906 | 		folder: 'cleanDataServices',
 907 | 		extension: 'cleanDataService'
 908 | 	},
 909 | 	CustomLabel: {
 910 | 		metadataType: 'CustomLabel',
 911 | 		enumName: 'CUSTOM_LABEL',
 912 | 		folder: 'CUSTOM_LABELS',
 913 | 		extension: ''
 914 | 	},
 915 | 	Index: {
 916 | 		metadataType: 'Index',
 917 | 		enumName: 'INDEX',
 918 | 		folder: 'CUSTOM_OBJECT',
 919 | 		extension: ''
 920 | 	},
 921 | 	ListView: {
 922 | 		metadataType: 'ListView',
 923 | 		enumName: 'LIST_VIEW',
 924 | 		folder: 'CUSTOM_OBJECT',
 925 | 		extension: ''
 926 | 	},
 927 | 	RecordType: {
 928 | 		metadataType: 'RecordType',
 929 | 		enumName: 'RECORD_TYPE',
 930 | 		folder: 'CUSTOM_OBJECT',
 931 | 		extension: ''
 932 | 	},
 933 | 	WebLink: {
 934 | 		metadataType: 'WebLink',
 935 | 		enumName: 'WEBLINK',
 936 | 		folder: 'CUSTOM_OBJECT',
 937 | 		extension: ''
 938 | 	},
 939 | 	WorkflowRule: {
 940 | 		metadataType: 'WorkflowRule',
 941 | 		enumName: 'WORKFLOW_RULE',
 942 | 		folder: 'WORKFLOW',
 943 | 		extension: ''
 944 | 	},
 945 | 	WorkflowTask: {
 946 | 		metadataType: 'WorkflowTask',
 947 | 		enumName: 'WORKFLOW_TASK',
 948 | 		folder: 'WORKFLOW',
 949 | 		extension: ''
 950 | 	},
 951 | 	WorkflowKnowledgePublish: {
 952 | 		metadataType: 'WorkflowKnowledgePublish',
 953 | 		enumName: 'WORKFLOW_KNOWLEDGE_PUBLISH',
 954 | 		folder: 'WORKFLOW',
 955 | 		extension: ''
 956 | 	},
 957 | 	WorkflowSend: {
 958 | 		metadataType: 'WorkflowSend',
 959 | 		enumName: 'WORKFLOW_SEND',
 960 | 		folder: 'WORKFLOW',
 961 | 		extension: ''
 962 | 	},
 963 | 	FieldSet: {
 964 | 		metadataType: 'FieldSet',
 965 | 		enumName: 'FIELD_SET',
 966 | 		folder: 'CUSTOM_OBJECT',
 967 | 		extension: ''
 968 | 	},
 969 | 	Group: {
 970 | 		metadataType: 'Group',
 971 | 		enumName: 'GROUP',
 972 | 		folder: 'groups',
 973 | 		extension: 'group'
 974 | 	},
 975 | 	PresenceDeclineReason: {
 976 | 		metadataType: 'PresenceDeclineReason',
 977 | 		enumName: 'PRESENCE_DECLINE_REASON',
 978 | 		folder: 'presenceDeclineReasons',
 979 | 		extension: 'presenceDeclineReason'
 980 | 	},
 981 | 	PresenceUserConfig: {
 982 | 		metadataType: 'PresenceUserConfig',
 983 | 		enumName: 'PRESENCE_USER_CONFIG',
 984 | 		folder: 'presenceUserConfigs',
 985 | 		extension: 'presenceUserConfig'
 986 | 	},
 987 | 	Queue: {
 988 | 		metadataType: 'Queue',
 989 | 		enumName: 'QUEUE',
 990 | 		folder: 'queues',
 991 | 		extension: 'queue'
 992 | 	},
 993 | 	QueueRoutingConfig: {
 994 | 		metadataType: 'QueueRoutingConfig',
 995 | 		enumName: 'QUEUE_ROUTING_CONFIG',
 996 | 		folder: 'queueRoutingConfigs',
 997 | 		extension: 'queueRoutingConfig'
 998 | 	},
 999 | 	SharingRules: {
1000 | 		metadataType: 'SharingRules',
1001 | 		enumName: 'SHARING_RULES',
1002 | 		folder: 'sharingRules',
1003 | 		extension: 'sharingRules'
1004 | 	},
1005 | 	SharingOwnerRule: {
1006 | 		metadataType: 'SharingOwnerRule',
1007 | 		enumName: 'SHARING_OWNER_RULES',
1008 | 		folder: 'SHARING_RULES',
1009 | 		extension: ''
1010 | 	},
1011 | 	SharingCriteriaRule: {
1012 | 		metadataType: 'SharingCriteriaRule',
1013 | 		enumName: 'SHARING_CRITERIA_RULES',
1014 | 		folder: 'SHARING_RULES',
1015 | 		extension: ''
1016 | 	},
1017 | 	SharingGuestRule: {
1018 | 		metadataType: 'SharingGuestRule',
1019 | 		enumName: 'SHARING_GUEST_RULES',
1020 | 		folder: 'SHARING_RULES',
1021 | 		extension: ''
1022 | 	},
1023 | 	SharingTerritoryRule: {
1024 | 		metadataType: 'SharingTerritoryRule',
1025 | 		enumName: 'SHARING_TERRITORY_RULES',
1026 | 		folder: 'SHARING_RULES',
1027 | 		extension: ''
1028 | 	},
1029 | 	Module: {
1030 | 		metadataType: 'Module',
1031 | 		enumName: 'MODULE',
1032 | 		folder: 'modules',
1033 | 		extension: 'null'
1034 | 	},
1035 | 	Application: {
1036 | 		metadataType: 'Application',
1037 | 		enumName: 'APPLICATION',
1038 | 		folder: 'applicationManifests',
1039 | 		extension: 'null'
1040 | 	},
1041 | 	CanvasMetadata: {
1042 | 		metadataType: 'CanvasMetadata',
1043 | 		enumName: 'CANVAS',
1044 | 		folder: 'Canvases',
1045 | 		extension: 'Canvas'
1046 | 	},
1047 | 	MobileApplicationDetail: {
1048 | 		metadataType: 'MobileApplicationDetail',
1049 | 		enumName: 'MOBILE_APPLICATION_DETAIL',
1050 | 		folder: 'MobileApplicationDetails',
1051 | 		extension: 'MobileApplicationDetail'
1052 | 	},
1053 | 	Network: {
1054 | 		metadataType: 'Network',
1055 | 		enumName: 'NETWORK',
1056 | 		folder: 'networks',
1057 | 		extension: 'network'
1058 | 	},
1059 | 	NetworkBranding: {
1060 | 		metadataType: 'NetworkBranding',
1061 | 		enumName: 'NETWORK_BRANDING',
1062 | 		folder: 'networkBranding',
1063 | 		extension: 'networkBranding'
1064 | 	},
1065 | 	NavigationMenu: {
1066 | 		metadataType: 'NavigationMenu',
1067 | 		enumName: 'NAVIGATION_MENU',
1068 | 		folder: 'navigationMenus',
1069 | 		extension: 'navigationMenu'
1070 | 	},
1071 | 	Certificate: {
1072 | 		metadataType: 'Certificate',
1073 | 		enumName: 'CERTIFICATE',
1074 | 		folder: 'certs',
1075 | 		extension: 'crt'
1076 | 	},
1077 | 	AuthProvider: {
1078 | 		metadataType: 'AuthProvider',
1079 | 		enumName: 'AUTH_PROVIDER',
1080 | 		folder: 'authproviders',
1081 | 		extension: 'authprovider'
1082 | 	},
1083 | 	SamlSsoConfig: {
1084 | 		metadataType: 'SamlSsoConfig',
1085 | 		enumName: 'SAML_SSO_CONFIG',
1086 | 		folder: 'samlssoconfigs',
1087 | 		extension: 'samlssoconfig'
1088 | 	},
1089 | 	MyDomainDiscoverableLogin: {
1090 | 		metadataType: 'MyDomainDiscoverableLogin',
1091 | 		enumName: 'MY_DOMAIN_DISCOVERABLE_LOGIN',
1092 | 		folder: 'myDomainDiscoverableLogins',
1093 | 		extension: 'myDomainDiscoverableLogin'
1094 | 	},
1095 | 	BlackListedConsumer: {
1096 | 		metadataType: 'BlackListedConsumer',
1097 | 		enumName: 'BLACKLISTED_CONSUMER',
1098 | 		folder: 'blackListedConsumers',
1099 | 		extension: 'blackListedConsumer'
1100 | 	},
1101 | 	InboundCertificate: {
1102 | 		metadataType: 'InboundCertificate',
1103 | 		enumName: 'INBOUND_CERTIFICATE',
1104 | 		folder: 'inboundCertificates',
1105 | 		extension: 'inboundCertificate'
1106 | 	},
1107 | 	UserProvisioningConfig: {
1108 | 		metadataType: 'UserProvisioningConfig',
1109 | 		enumName: 'USER_PROVISIONING_CONFIG',
1110 | 		folder: 'userProvisioningConfigs',
1111 | 		extension: 'userProvisioningConfig'
1112 | 	},
1113 | 	CaseSettings: {
1114 | 		metadataType: 'CaseSettings',
1115 | 		enumName: 'CASE_SETTINGS',
1116 | 		folder: 'settings',
1117 | 		extension: 'settings'
1118 | 	},
1119 | 	MacroSettings: {
1120 | 		metadataType: 'MacroSettings',
1121 | 		enumName: 'MACRO_SETTINGS',
1122 | 		folder: 'settings',
1123 | 		extension: 'settings'
1124 | 	},
1125 | 	QuickTextSettings: {
1126 | 		metadataType: 'QuickTextSettings',
1127 | 		enumName: 'QUICK_TEXT_SETTINGS',
1128 | 		folder: 'settings',
1129 | 		extension: 'settings'
1130 | 	},
1131 | 	WebToXSettings: {
1132 | 		metadataType: 'WebToXSettings',
1133 | 		enumName: 'WEB_TO_X_SETTINGS',
1134 | 		folder: 'settings',
1135 | 		extension: 'settings'
1136 | 	},
1137 | 	ManagedTopics: {
1138 | 		metadataType: 'ManagedTopics',
1139 | 		enumName: 'MANAGED_TOPICS',
1140 | 		folder: 'managedTopics',
1141 | 		extension: 'managedTopics'
1142 | 	},
1143 | 	ManagedTopic: {
1144 | 		metadataType: 'ManagedTopic',
1145 | 		enumName: 'MANAGED_TOPIC',
1146 | 		folder: 'MANAGED_TOPICS',
1147 | 		extension: ''
1148 | 	},
1149 | 	TopicsForObjects: {
1150 | 		metadataType: 'TopicsForObjects',
1151 | 		enumName: 'TOPICS_FOR_OBJECTS',
1152 | 		folder: 'topicsForObjects',
1153 | 		extension: 'topicsForObjects'
1154 | 	},
1155 | 	SiteSettings: {
1156 | 		metadataType: 'SiteSettings',
1157 | 		enumName: 'SITE_SETTINGS',
1158 | 		folder: 'settings',
1159 | 		extension: 'settings'
1160 | 	},
1161 | 	QuickAction: {
1162 | 		metadataType: 'QuickAction',
1163 | 		enumName: 'QUICK_ACTION_DEFINITION',
1164 | 		folder: 'quickActions',
1165 | 		extension: 'quickAction'
1166 | 	},
1167 | 	SiteDotCom: {
1168 | 		metadataType: 'SiteDotCom',
1169 | 		enumName: 'SITEDOTCOM',
1170 | 		folder: 'siteDotComSites',
1171 | 		extension: 'site'
1172 | 	},
1173 | 	KnowledgeSettings: {
1174 | 		metadataType: 'KnowledgeSettings',
1175 | 		enumName: 'KNOWLEDGE_SETTINGS',
1176 | 		folder: 'settings',
1177 | 		extension: 'settings'
1178 | 	},
1179 | 	AddressSettings: {
1180 | 		metadataType: 'AddressSettings',
1181 | 		enumName: 'ADDRESS_SETTINGS',
1182 | 		folder: 'settings',
1183 | 		extension: 'settings'
1184 | 	},
1185 | 	ContractSettings: {
1186 | 		metadataType: 'ContractSettings',
1187 | 		enumName: 'CONTRACT_SETTINGS',
1188 | 		folder: 'settings',
1189 | 		extension: 'settings'
1190 | 	},
1191 | 	CompanySettings: {
1192 | 		metadataType: 'CompanySettings',
1193 | 		enumName: 'COMPANY_SETTINGS',
1194 | 		folder: 'settings',
1195 | 		extension: 'settings'
1196 | 	},
1197 | 	OpportunitySettings: {
1198 | 		metadataType: 'OpportunitySettings',
1199 | 		enumName: 'OPPORTUNITY_SETTINGS',
1200 | 		folder: 'settings',
1201 | 		extension: 'settings'
1202 | 	},
1203 | 	ProductSettings: {
1204 | 		metadataType: 'ProductSettings',
1205 | 		enumName: 'PRODUCT_SETTINGS',
1206 | 		folder: 'settings',
1207 | 		extension: 'settings'
1208 | 	},
1209 | 	QuoteSettings: {
1210 | 		metadataType: 'QuoteSettings',
1211 | 		enumName: 'QUOTE_SETTINGS',
1212 | 		folder: 'settings',
1213 | 		extension: 'settings'
1214 | 	},
1215 | 	ForecastingSettings: {
1216 | 		metadataType: 'ForecastingSettings',
1217 | 		enumName: 'FORECASTING_SETTINGS',
1218 | 		folder: 'settings',
1219 | 		extension: 'settings'
1220 | 	},
1221 | 	ActivitiesSettings: {
1222 | 		metadataType: 'ActivitiesSettings',
1223 | 		enumName: 'ACTIVITIES_SETTINGS',
1224 | 		folder: 'settings',
1225 | 		extension: 'settings'
1226 | 	},
1227 | 	AccountSettings: {
1228 | 		metadataType: 'AccountSettings',
1229 | 		enumName: 'ACCOUNT_SETTINGS',
1230 | 		folder: 'settings',
1231 | 		extension: 'settings'
1232 | 	},
1233 | 	InstalledPackage: {
1234 | 		metadataType: 'InstalledPackage',
1235 | 		enumName: 'INSTALLED_PACKAGE',
1236 | 		folder: 'installedPackages',
1237 | 		extension: 'installedPackage'
1238 | 	},
1239 | 	CompactLayout: {
1240 | 		metadataType: 'CompactLayout',
1241 | 		enumName: 'COMPACT_LAYOUT',
1242 | 		folder: 'CUSTOM_OBJECT',
1243 | 		extension: ''
1244 | 	},
1245 | 	BusinessHoursSettings: {
1246 | 		metadataType: 'BusinessHoursSettings',
1247 | 		enumName: 'BUSINESS_HOURS_SETTINGS',
1248 | 		folder: 'settings',
1249 | 		extension: 'settings'
1250 | 	},
1251 | 	ExperienceBundle: {
1252 | 		metadataType: 'ExperienceBundle',
1253 | 		enumName: 'EXPERIENCE_BUNDLE',
1254 | 		folder: 'experiences',
1255 | 		extension: 'null'
1256 | 	},
1257 | 	ExperienceBundleSettings: {
1258 | 		metadataType: 'ExperienceBundleSettings',
1259 | 		enumName: 'EXPERIENCE_BUNDLE_SETTINGS',
1260 | 		folder: 'settings',
1261 | 		extension: 'settings'
1262 | 	},
1263 | 	XOrgHub: {
1264 | 		metadataType: 'XOrgHub',
1265 | 		enumName: 'XORG_HUB',
1266 | 		folder: 'xorghubs',
1267 | 		extension: 'xorghub'
1268 | 	},
1269 | 	XOrgSpoke: {
1270 | 		metadataType: 'XOrgSpoke',
1271 | 		enumName: 'XORG_SPOKE',
1272 | 		folder: 'xorgspokes',
1273 | 		extension: 'xorgspoke'
1274 | 	},
1275 | 	SharingSet: {
1276 | 		metadataType: 'SharingSet',
1277 | 		enumName: 'SHARING_SET',
1278 | 		folder: 'sharingSets',
1279 | 		extension: 'sharingSet'
1280 | 	},
1281 | 	AppMenu: {
1282 | 		metadataType: 'AppMenu',
1283 | 		enumName: 'APP_MENU',
1284 | 		folder: 'appMenus',
1285 | 		extension: 'appMenu'
1286 | 	},
1287 | 	CustomPermission: {
1288 | 		metadataType: 'CustomPermission',
1289 | 		enumName: 'CUSTOM_PERMISSION',
1290 | 		folder: 'customPermissions',
1291 | 		extension: 'customPermission'
1292 | 	},
1293 | 	DelegateGroup: {
1294 | 		metadataType: 'DelegateGroup',
1295 | 		enumName: 'DELEGATE_GROUP',
1296 | 		folder: 'delegateGroups',
1297 | 		extension: 'delegateGroup'
1298 | 	},
1299 | 	ExternalServiceRegistration: {
1300 | 		metadataType: 'ExternalServiceRegistration',
1301 | 		enumName: 'EXTERNAL_SERVICE_REGISTRATION',
1302 | 		folder: 'externalServiceRegistrations',
1303 | 		extension: 'externalServiceRegistration'
1304 | 	},
1305 | 	LicenseDefinition: {
1306 | 		metadataType: 'LicenseDefinition',
1307 | 		enumName: 'LICENSE_DEFINITION',
1308 | 		folder: 'licenseDefinitions',
1309 | 		extension: 'licenseDefinition'
1310 | 	},
1311 | 	CampaignInfluenceModel: {
1312 | 		metadataType: 'CampaignInfluenceModel',
1313 | 		enumName: 'CAMPAIGN_INFLUENCE_MODEL',
1314 | 		folder: 'campaignInfluenceModels',
1315 | 		extension: 'campaignInfluenceModel'
1316 | 	},
1317 | 	SurveySettings: {
1318 | 		metadataType: 'SurveySettings',
1319 | 		enumName: 'SURVEY_SETTINGS',
1320 | 		folder: 'settings',
1321 | 		extension: 'settings'
1322 | 	},
1323 | 	IndustriesManufacturingSettings: {
1324 | 		metadataType: 'IndustriesManufacturingSettings',
1325 | 		enumName: 'INDUSTRIES_MANUFACTURING_SETTINGS',
1326 | 		folder: 'settings',
1327 | 		extension: 'settings'
1328 | 	},
1329 | 	Territory2Settings: {
1330 | 		metadataType: 'Territory2Settings',
1331 | 		enumName: '_SETTINGS',
1332 | 		folder: 'settings',
1333 | 		extension: 'settings'
1334 | 	},
1335 | 	Territory2Type: {
1336 | 		metadataType: 'Territory2Type',
1337 | 		enumName: '_TYPE',
1338 | 		folder: 'territory2Types',
1339 | 		extension: 'territory2Type'
1340 | 	},
1341 | 	Territory2Model: {
1342 | 		metadataType: 'Territory2Model',
1343 | 		enumName: '_MODEL',
1344 | 		folder: 'territory2Models',
1345 | 		extension: 'territory2Model'
1346 | 	},
1347 | 	Territory2Rule: {
1348 | 		metadataType: 'Territory2Rule',
1349 | 		enumName: '_RULE',
1350 | 		folder: 'territory2Models',
1351 | 		extension: 'territory2Rule'
1352 | 	},
1353 | 	UiPlugin: {
1354 | 		metadataType: 'UiPlugin',
1355 | 		enumName: 'UI_PLUGIN',
1356 | 		folder: 'uiplugins',
1357 | 		extension: 'uiplugin'
1358 | 	},
1359 | 	PathAssistantSettings: {
1360 | 		metadataType: 'PathAssistantSettings',
1361 | 		enumName: 'PATH_ASSISTANT_SETTINGS',
1362 | 		folder: 'settings',
1363 | 		extension: 'settings'
1364 | 	},
1365 | 	PathAssistant: {
1366 | 		metadataType: 'PathAssistant',
1367 | 		enumName: 'PATH_ASSISTANT',
1368 | 		folder: 'pathAssistants',
1369 | 		extension: 'pathAssistant'
1370 | 	},
1371 | 	GlobalPicklist: {
1372 | 		metadataType: 'GlobalPicklist',
1373 | 		enumName: 'GLOBAL_PICKLIST',
1374 | 		folder: 'globalPicklists',
1375 | 		extension: 'globalPicklist'
1376 | 	},
1377 | 	GlobalValueSet: {
1378 | 		metadataType: 'GlobalValueSet',
1379 | 		enumName: 'GLOBAL_VALUE_SET',
1380 | 		folder: 'globalValueSets',
1381 | 		extension: 'globalValueSet'
1382 | 	},
1383 | 	GlobalValueSetTranslation: {
1384 | 		metadataType: 'GlobalValueSetTranslation',
1385 | 		enumName: 'GLOBAL_VALUE_SET_TRANSLATION',
1386 | 		folder: 'globalValueSetTranslations',
1387 | 		extension: 'globalValueSetTranslation'
1388 | 	},
1389 | 	StandardValueSet: {
1390 | 		metadataType: 'StandardValueSet',
1391 | 		enumName: 'STANDARD_VALUE_SET',
1392 | 		folder: 'standardValueSets',
1393 | 		extension: 'standardValueSet'
1394 | 	},
1395 | 	StandardValueSetTranslation: {
1396 | 		metadataType: 'StandardValueSetTranslation',
1397 | 		enumName: 'STANDARD_VALUE_SET_TRANSLATION',
1398 | 		folder: 'standardValueSetTranslations',
1399 | 		extension: 'standardValueSetTranslation'
1400 | 	},
1401 | 	LanguageSettings: {
1402 | 		metadataType: 'LanguageSettings',
1403 | 		enumName: 'LANGUAGE_SETTINGS',
1404 | 		folder: 'settings',
1405 | 		extension: 'settings'
1406 | 	},
1407 | 	CurrencySettings: {
1408 | 		metadataType: 'CurrencySettings',
1409 | 		enumName: 'CURRENCY_SETTINGS',
1410 | 		folder: 'settings',
1411 | 		extension: 'settings'
1412 | 	},
1413 | 	EclairGeoData: {
1414 | 		metadataType: 'EclairGeoData',
1415 | 		enumName: 'ECLAIRGEODATA',
1416 | 		folder: 'eclair',
1417 | 		extension: 'geodata'
1418 | 	},
1419 | 	WaveApplication: {
1420 | 		metadataType: 'WaveApplication',
1421 | 		enumName: 'WAVEAPPLICATION',
1422 | 		folder: 'wave',
1423 | 		extension: 'wapp'
1424 | 	},
1425 | 	WaveDataset: {
1426 | 		metadataType: 'WaveDataset',
1427 | 		enumName: 'WAVEDATASET',
1428 | 		folder: 'wave',
1429 | 		extension: 'wds'
1430 | 	},
1431 | 	WaveLens: {
1432 | 		metadataType: 'WaveLens',
1433 | 		enumName: 'WAVELENS',
1434 | 		folder: 'wave',
1435 | 		extension: 'wlens'
1436 | 	},
1437 | 	WaveDashboard: {
1438 | 		metadataType: 'WaveDashboard',
1439 | 		enumName: 'WAVEDASHBOARD',
1440 | 		folder: 'wave',
1441 | 		extension: 'wdash'
1442 | 	},
1443 | 	WaveDataflow: {
1444 | 		metadataType: 'WaveDataflow',
1445 | 		enumName: 'WAVEDATAFLOW',
1446 | 		folder: 'wave',
1447 | 		extension: 'wdf'
1448 | 	},
1449 | 	WaveRecipe: {
1450 | 		metadataType: 'WaveRecipe',
1451 | 		enumName: 'WAVERECIPE',
1452 | 		folder: 'wave',
1453 | 		extension: 'wdpr'
1454 | 	},
1455 | 	WaveXmd: {
1456 | 		metadataType: 'WaveXmd',
1457 | 		enumName: 'WAVEXMD',
1458 | 		folder: 'wave',
1459 | 		extension: 'xmd'
1460 | 	},
1461 | 	EventSubscription: {
1462 | 		metadataType: 'EventSubscription',
1463 | 		enumName: 'EVENT_SUBSCRIPTION',
1464 | 		folder: 'eventSubscriptions',
1465 | 		extension: 'subscription'
1466 | 	},
1467 | 	EventDelivery: {
1468 | 		metadataType: 'EventDelivery',
1469 | 		enumName: 'EVENT_DELIVERY',
1470 | 		folder: 'eventDeliveries',
1471 | 		extension: 'delivery'
1472 | 	},
1473 | 	KeywordList: {
1474 | 		metadataType: 'KeywordList',
1475 | 		enumName: 'KEYWORD_LIST',
1476 | 		folder: 'moderation',
1477 | 		extension: 'keywords'
1478 | 	},
1479 | 	ModerationRule: {
1480 | 		metadataType: 'ModerationRule',
1481 | 		enumName: 'MODERATION_RULE',
1482 | 		folder: 'moderation',
1483 | 		extension: 'rule'
1484 | 	},
1485 | 	UserCriteria: {
1486 | 		metadataType: 'UserCriteria',
1487 | 		enumName: 'USER_CRITERIA',
1488 | 		folder: 'userCriteria',
1489 | 		extension: 'userCriteria'
1490 | 	},
1491 | 	BrandingSet: {
1492 | 		metadataType: 'BrandingSet',
1493 | 		enumName: 'BRANDING_SET',
1494 | 		folder: 'brandingSets',
1495 | 		extension: 'brandingSet'
1496 | 	},
1497 | 	CommunityTemplateDefinition: {
1498 | 		metadataType: 'CommunityTemplateDefinition',
1499 | 		enumName: 'COMMUNITY_TEMPLATE_DEFINITION',
1500 | 		folder: 'communityTemplateDefinitions',
1501 | 		extension: 'communityTemplateDefinition'
1502 | 	},
1503 | 	CommunityThemeDefinition: {
1504 | 		metadataType: 'CommunityThemeDefinition',
1505 | 		enumName: 'COMMUNITY_THEME_DEFINITION',
1506 | 		folder: 'communityThemeDefinitions',
1507 | 		extension: 'communityThemeDefinition'
1508 | 	},
1509 | 	IoTSettings: {
1510 | 		metadataType: 'IoTSettings',
1511 | 		enumName: 'IOT_SETTINGS',
1512 | 		folder: 'settings',
1513 | 		extension: 'settings'
1514 | 	},
1515 | 	SocialCustomerServiceSettings: {
1516 | 		metadataType: 'SocialCustomerServiceSettings',
1517 | 		enumName: 'SCS_SETTINGS',
1518 | 		folder: 'settings',
1519 | 		extension: 'settings'
1520 | 	},
1521 | 	CaseSubjectParticle: {
1522 | 		metadataType: 'CaseSubjectParticle',
1523 | 		enumName: 'CASE_SUBJECT_PARTICLE',
1524 | 		folder: 'CaseSubjectParticles',
1525 | 		extension: 'CaseSubjectParticle'
1526 | 	},
1527 | 	AIApplication: {
1528 | 		metadataType: 'AIApplication',
1529 | 		enumName: 'AI_APPLICATION',
1530 | 		folder: 'aiApplications',
1531 | 		extension: 'ai'
1532 | 	},
1533 | 	AIModel: {
1534 | 		metadataType: 'AIModel',
1535 | 		enumName: 'AI_MODEL',
1536 | 		folder: 'aiModels',
1537 | 		extension: 'aimodel'
1538 | 	},
1539 | 	BotSettings: {
1540 | 		metadataType: 'BotSettings',
1541 | 		enumName: 'BOT_SETTINGS',
1542 | 		folder: 'settings',
1543 | 		extension: 'settings'
1544 | 	},
1545 | 	Bot: {
1546 | 		metadataType: 'Bot',
1547 | 		enumName: 'BOT',
1548 | 		folder: 'bots',
1549 | 		extension: 'bot'
1550 | 	},
1551 | 	BotVersion: {
1552 | 		metadataType: 'BotVersion',
1553 | 		enumName: 'BOT_VERSION',
1554 | 		folder: 'BOT',
1555 | 		extension: ''
1556 | 	},
1557 | 	MlDomain: {
1558 | 		metadataType: 'MlDomain',
1559 | 		enumName: 'ML_DOMAIN',
1560 | 		folder: 'mlDomains',
1561 | 		extension: 'mlDomain'
1562 | 	},
1563 | 	CMSConnectSource: {
1564 | 		metadataType: 'CMSConnectSource',
1565 | 		enumName: 'CMS_CONNECT_SOURCE',
1566 | 		folder: 'cmsConnectSource',
1567 | 		extension: 'cmsConnectSource'
1568 | 	},
1569 | 	ManagedContentType: {
1570 | 		metadataType: 'ManagedContentType',
1571 | 		enumName: 'MANAGED_CONTENT_TYPE',
1572 | 		folder: 'managedContentTypes',
1573 | 		extension: 'managedContentType'
1574 | 	},
1575 | 	RecommendationStrategy: {
1576 | 		metadataType: 'RecommendationStrategy',
1577 | 		enumName: 'RECOMMENDATION_STRATEGY',
1578 | 		folder: 'recommendationStrategies',
1579 | 		extension: 'recommendationStrategy'
1580 | 	},
1581 | 	CommunityAIModelMapping: {
1582 | 		metadataType: 'CommunityAIModelMapping',
1583 | 		enumName: 'COMMUNITY_AI_MODEL_MAPPING',
1584 | 		folder: 'communityAIModelMappings',
1585 | 		extension: 'communityAIModelMapping'
1586 | 	},
1587 | 	ExternalAIModel: {
1588 | 		metadataType: 'ExternalAIModel',
1589 | 		enumName: 'EXTERNAL_AI_MDOEL',
1590 | 		folder: 'externalAIModels',
1591 | 		extension: 'externalAIModel'
1592 | 	},
1593 | 	AIReplyRecommendationsSettings: {
1594 | 		metadataType: 'AIReplyRecommendationsSettings',
1595 | 		enumName: 'AI_REPLY_RECOMMENDATIONS',
1596 | 		folder: 'settings',
1597 | 		extension: 'settings'
1598 | 	},
1599 | 	CaseClassificationSettings: {
1600 | 		metadataType: 'CaseClassificationSettings',
1601 | 		enumName: 'CASE_CLASSIFICATION',
1602 | 		folder: 'settings',
1603 | 		extension: 'settings'
1604 | 	},
1605 | 	AccessControlPolicy: {
1606 | 		metadataType: 'AccessControlPolicy',
1607 | 		enumName: 'ACCESS_CONTROL_POLICY',
1608 | 		folder: 'accessControlPolicies',
1609 | 		extension: 'policy'
1610 | 	},
1611 | 	PlatformEventChannel: {
1612 | 		metadataType: 'PlatformEventChannel',
1613 | 		enumName: 'PLATFORM_EVENT_CHANNEL',
1614 | 		folder: 'platformEventChannels',
1615 | 		extension: 'platformEventChannel'
1616 | 	},
1617 | 	PlatformEventChannelMember: {
1618 | 		metadataType: 'PlatformEventChannelMember',
1619 | 		enumName: 'PLATFORM_EVENT_CHANNEL_MEMBER',
1620 | 		folder: 'platformEventChannelMembers',
1621 | 		extension: 'platformEventChannelMember'
1622 | 	},
1623 | 	RecordActionDeployment: {
1624 | 		metadataType: 'RecordActionDeployment',
1625 | 		enumName: 'RECORD_ACTION_DEPLOYMENT',
1626 | 		folder: 'recordActionDeployments',
1627 | 		extension: 'deployment'
1628 | 	},
1629 | 	AccountRelationshipShareRule: {
1630 | 		metadataType: 'AccountRelationshipShareRule',
1631 | 		enumName: 'ACCOUNT_RELATIONSHIP_SHARE_RULE',
1632 | 		folder: 'accountRelationshipShareRules',
1633 | 		extension: 'accountRelationshipShareRule'
1634 | 	},
1635 | 	UIObjectRelationConfig: {
1636 | 		metadataType: 'UIObjectRelationConfig',
1637 | 		enumName: 'UI_OBJECT_RELATION_CONFIG',
1638 | 		folder: 'uiObjectRelationConfigs',
1639 | 		extension: 'uiObjectRelationConfig'
1640 | 	},
1641 | 	TimeSheetTemplate: {
1642 | 		metadataType: 'TimeSheetTemplate',
1643 | 		enumName: 'TIME_SHEET_TEMPLATE',
1644 | 		folder: 'timeSheetTemplates',
1645 | 		extension: 'timeSheetTemplate'
1646 | 	},
1647 | 	AppointmentSchedulingPolicy: {
1648 | 		metadataType: 'AppointmentSchedulingPolicy',
1649 | 		enumName: 'APPOINTMENT_SCHEDULING_POLICY',
1650 | 		folder: 'appointmentSchedulingPolicies',
1651 | 		extension: 'policy'
1652 | 	},
1653 | 	NotificationsSettings: {
1654 | 		metadataType: 'NotificationsSettings',
1655 | 		enumName: 'NOTIFICATIONS_SETTINGS',
1656 | 		folder: 'settings',
1657 | 		extension: 'settings'
1658 | 	},
1659 | 	LightningExperienceSettings: {
1660 | 		metadataType: 'LightningExperienceSettings',
1661 | 		enumName: 'LIGHTNING_EXPERIENCE_SETTINGS',
1662 | 		folder: 'settings',
1663 | 		extension: 'settings'
1664 | 	},
1665 | 	Prompt: {
1666 | 		metadataType: 'Prompt',
1667 | 		enumName: 'PROMPT',
1668 | 		folder: 'prompts',
1669 | 		extension: 'prompt'
1670 | 	},
1671 | 	PaymentGatewayProvider: {
1672 | 		metadataType: 'PaymentGatewayProvider',
1673 | 		enumName: 'PAYMENT_GATEWAY_PROVIDER',
1674 | 		folder: 'paymentGatewayProviders',
1675 | 		extension: 'paymentGatewayProvider'
1676 | 	},
1677 | 	AnimationRule: {
1678 | 		metadataType: 'AnimationRule',
1679 | 		enumName: 'ANIMATIONRULE',
1680 | 		folder: 'animationRules',
1681 | 		extension: 'animationRule'
1682 | 	},
1683 | 	LightningMessageChannel: {
1684 | 		metadataType: 'LightningMessageChannel',
1685 | 		enumName: 'LIGHTNING_MESSAGE_CHANNEL',
1686 | 		folder: 'messageChannels',
1687 | 		extension: 'messageChannel'
1688 | 	},
1689 | 	OrderManagementSettings: {
1690 | 		metadataType: 'OrderManagementSettings',
1691 | 		enumName: 'ORDER_MANAGEMENT_SETTINGS',
1692 | 		folder: 'settings',
1693 | 		extension: 'settings'
1694 | 	},
1695 | 	BlockchainSettings: {
1696 | 		metadataType: 'BlockchainSettings',
1697 | 		enumName: 'BLOCKCHAIN_SETTINGS',
1698 | 		folder: 'settings',
1699 | 		extension: 'settings'
1700 | 	},
1701 | 	PartyDataModelSettings: {
1702 | 		metadataType: 'PartyDataModelSettings',
1703 | 		enumName: 'PARTY_DATA_MODEL_SETTINGS',
1704 | 		folder: 'settings',
1705 | 		extension: 'settings'
1706 | 	},
1707 | 	RecordPageSettings: {
1708 | 		metadataType: 'RecordPageSettings',
1709 | 		enumName: 'RECORD_PAGE_SETTINGS',
1710 | 		folder: 'settings',
1711 | 		extension: 'settings'
1712 | 	},
1713 | 	RetailExecutionSettings: {
1714 | 		metadataType: 'RetailExecutionSettings',
1715 | 		enumName: 'RETAIL_EXECUTION_SETTINGS',
1716 | 		folder: 'settings',
1717 | 		extension: 'settings'
1718 | 	},
1719 | 	OpportunityInsightsSettings: {
1720 | 		metadataType: 'OpportunityInsightsSettings',
1721 | 		enumName: 'OPPORTUNITY_INSIGHTS_SETTINGS',
1722 | 		folder: 'settings',
1723 | 		extension: 'settings'
1724 | 	},
1725 | 	AccountInsightsSettings: {
1726 | 		metadataType: 'AccountInsightsSettings',
1727 | 		enumName: 'ACCOUNT_INSIGHTS_SETTINGS',
1728 | 		folder: 'settings',
1729 | 		extension: 'settings'
1730 | 	},
1731 | 	NotificationTypeConfig: {
1732 | 		metadataType: 'NotificationTypeConfig',
1733 | 		enumName: 'NOTIFICATION_TYPE_CONFIG',
1734 | 		folder: 'notificationTypeConfig',
1735 | 		extension: 'config'
1736 | 	},
1737 | 	AutomatedContactsSettings: {
1738 | 		metadataType: 'AutomatedContactsSettings',
1739 | 		enumName: 'AUTOMATED_CONTACTS_SETTINGS',
1740 | 		folder: 'settings',
1741 | 		extension: 'settings'
1742 | 	},
1743 | 	MigrationAlias: {
1744 | 		metadataType: 'MigrationAlias',
1745 | 		enumName: 'MIGRATION_RECORD_ALIAS',
1746 | 		folder: 'migrationAliases',
1747 | 		extension: 'alias'
1748 | 	},
1749 | 	AccountIntelligenceSettings: {
1750 | 		metadataType: 'AccountIntelligenceSettings',
1751 | 		enumName: 'ACCOUNT_INTELLIGENCE_SETTINGS',
1752 | 		folder: 'settings',
1753 | 		extension: 'settings'
1754 | 	},
1755 | 
1756 | }
1757 | 
1758 | export { mdmap };


--------------------------------------------------------------------------------