Skip to content

Org Maintenance

Jannis Schreiber edited this page Dec 26, 2024 · 18 revisions

Maintenance commands help you keep your Org tidy. The vision for this family is to be the one-stop shop to find and retrieve unpackaged components or purge deprecated components after package upgrades. They use the Tooling API and essentially prepare package manifests that you can inspect and use.

Garbage Commands

Garbage commands analyze your Org for things you don't need anymore and that are safe to delete. They only process components that used to be packaged.

Note

When a package is installed, the Package2Member record holds the information which metadata component belongs to which package. There are several reasons, why components cannot be not deleted from the target org when a new package version is installed that has components removed. The most popular are: The package was upgraded with DeprecateOnly upgrade type, the component could not be deleted, because it was still referenced (mostly UI components like LWC or QuickActions that are still referenced on Layouts or ApexClasses referenced by outdated flow versions), or, simply because the component is never deleted by design (CustomFields or CustomMetadataRecords).

All commands in this subtopic analyze the Package2Member entity to find deprecated components. The Tooling API is used to analyze them and prepare a manifest for retrieval (package.xml) or deletion (destructiveChanges.xml). By nature, garbage commands are bound by the scope of the Package2Member and never catch metadata that is not packaged.

Garbage Collector

The garbage collector (sf jsc maintain garbage collect) finds components that were removed from package source. It exclusively uses the Org as reference, no git diffs are performed. Most of the time, the orgs in your pipeline will be slightly different. When you run it against an org with no packages installed (or the packages have never been upgraded), the result will be empty:

{
  "status": 0,
  "result": {
    "deprecatedMembers": {},
    "ignoredTypes": {},
    "notImplementedTypes": [],
    "totalDeprecatedComponentCount": 0
  },
  "warnings": []
}

Now consider a package upgrade where you remove a CustomLabel and a CustomField. After the new package version is installed, the garbage collector will yield the following result:

{
  "status": 0,
  "result": {
    "deprecatedMembers": {
       "ExternalString": {
        "metadataType": "CustomLabel",
        "componentCount": 1,
        "components": [
          {
            "developerName": "My_Custom_Label",
            "fullyQualifiedName": "My_Custom_Label",
            "subjectId": "1010X00000AVjnmQAD"
          }
        ]
      },
      "CustomField": {
        "metadataType": "CustomField",
        "componentCount": 1,
        "components": [
          {
            "developerName": "MyCustomField",
            "fullyQualifiedName": "MyCustomObject__c.MyCustomField__c",
            "subjectId": "00N9Q00000F5jokUAB"
          },
        ]
      }
    },
    "ignoredTypes": {},
    "notImplementedTypes": [],
    "totalDeprecatedComponentCount": 2
  },
  "warnings": []
}

You can also create a manifest directly like this

sf jsc maintain garbage collect --target-org MySandbox --output-dir tmp/test-1 --output-format DestructiveChangesXML

Which will create the following destructiveChanges.xml with an empty package.xml at tmp/test-1:

<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>My_Custom_Label</members>
        <name>CustomLabel</name>
    </types>
    <types>
        <members>MyCustomObject__c.MyCustomField__c</members>
        <name>CustomField</name>
    </types>
    <version>62.0</version>
</Package>

Garbage Cleaner

Reserved. Not implemented yet.

Specific behavior for certain Metadata Types

Some metadata types are different and require special treatment. This section explains the design decisions behind some not-so-intuitive behavior.

Flows and FlowDefinitions

When you create a new flow, it is packaged as a FlowDefinition (prefix 300). When the flow is deployed (as part of a package install), it is actually deployed as a flow version (which is the Flow entity, prefix 301). You cannot change or delete FlowDefinitions directly, therefore the garbage commands retrieve all flow versions of a packaged "flow definition".

In source driven development, there is absolutely no reason to keep old flow versions. When you roll back, you either roll back to a previous commit, or you install an older package version. In both cases, when the flow was changed, a new flow version is created and set to active. Even if it was identical to a old version, you never reactivate the old one. Therefore, all outdated flow versions are treated as garbage, and are deleted.

Custom Fields and Custom Objects

When a custom object or one of its fields is deleted (not purged), it is still on your org for 30 days. Salesforce appends the suffix _del (so MyField__c will become MyField_del__c). Unfortunately, these fields are still returned by the Tooling API and I have not yet found a reliable way to exclude them from results. Always analyze the manifest content before doing something with it!

Custom Metadata Records

Custom Metadata Objects (and Fields) are custom objects. The entity definitions are deployed and retrieved as CustomObject and CustomField. However, records of these entities are also their own types, and you retrieve them separately: You filter for the custom metadata type itself, e.g. MyCustomMetadata__mdt).

The entity definitions are only included in garbage, if the definitions itself were removed from a package. The records, however, are included if the individual records were removed.

Clutter Commands

When deprecated components are garbage, all unpackaged components are clutter. Not happy soup. Not implemented yet.

Clone this wiki locally