Deployment issues with Process Builder Flows

There are some goodies in version 34.0 that should help you out. As you already noticed 196 added the concept of FlowDefinition that should allow you to activate/deactivate processes (flows) via the MD API. 196 also solve the problems around continuous integration by adding a permission you can turn on: FlowMdOverwriteEnabled. Flip this perm on and you will be able to overwrite active versions as well.

For anyone reading this post: Use this perm carefully because it will bypass every guard we have put in place to accidentally avoid overwriting an active process/flow.

Update:

The permission to request on the Case you raise is Enable MD Deploy to overwrite flows.


I wrote an ant target to help with the deployment of flows.

Before deploying flows, I fetch the flow definitions, then only deploy flows that are newer than the currently active version.

This assumes that you keep your metadata in a directory named src, so your flows are in src/flows and flow definitions in src/flowDefinitions. Deployment is done from a build directory that is populated by ant.

A script task is used. Refer to the ant documentation regarding dependencies for javascript tasks.

<target name="deploy" depends="prepBuildDir,prepFlows">
   <sf:deploy username="${sf.username}" password="${sf.password}" serverurl="${sf.serverurl}" deployRoot="build" singlePackage="true" maxPoll="${sf.maxPoll}" pollWaitMillis="${sf.maxWaitMillis}"  testLevel="${test.level}" checkOnly="${check.only}" logType="${log.type}" ignoreWarnings="true" />
</target>

<target name="prepBuildDir">
   <delete dir="build"/>
   <mkdir dir="build"/>
   <sync todir="build">
      <fileset dir="src">
         <exclude name="flows/*"/>
         <exclude name="flowDefinitions/*"/>
      </fileset>
   </sync>
</target>

<target name="prepFlows" depends="prepBuildDir,retrieveCurrentFlows,findFlowsToDeploy"/>

<target name="retrieveCurrentFlows" depends="orgType,setCredentials">
   <sf:retrieve username="${sf.username}" password="${sf.password}" serverurl="${sf.serverurl}" retrieveTarget="build" unpackaged="flowDefinitions.xml" />
</target>

<!--
     Compare retrieved flowDefinitions to ones in src
     If different and activeVersionNumber in src is greater, copy flow with activeVersionNumber to build/flows
-->
<target name="findFlowsToDeploy">
   <script language="javascript"><![CDATA[
      var File = java.io.File;

      function version(xml) {
         var matches = (''+ xml).match(/activeVersionNumber>(\d+)</);
         if (matches) {
            return parseInt(matches[1], 10);
         }
      }

      function getFileContents(javaFile) {
         var contents = '';
         var lines = java.nio.file.Files.readAllLines(java.nio.file.Paths.get(javaFile.getPath()), java.nio.charset.Charset.forName('UTF-8'));
         for (var i = 0; i < lines.size(); i++) {
            contents += lines.get(i);
         }
         return contents;
      }

      function getFlowVersion(flowFile) {
         var content = getFileContents(flowFile);
         return version(content);
      }

      function addFileToFileset(fileset, path) {
         java.lang.System.out.println('Flow needs to be deployed: ' + path);
         fileset.appendIncludes([path]);
      }

      function copyFiles(files, dest) {
         var copyTask = project.createTask('copy');
         if (typeof files === 'string') {
            copyTask.setFile(new File(files));
         } else {
            copyTask.add(files);
         }
         copyTask.setTodir(new File(dest));
         copyTask.setOverwrite(true);
         copyTask.perform();
      }

      function createFileset(dir) {
         var fileset = project.createDataType('fileset');
         fileset.setDir(new File(dir));
         return fileset;
      };

      function flowName(flowDefinition, version) {
         return flowDefinition.replace('.flowDefinition', '') + '-' + version + '.flow';
      }

      var flowsToDeploy = createFileset('src/flows');
      var flowDefinitions = createFileset('src/flowDefinitions');

      var srcFiles = flowDefinitions.getDirectoryScanner(project).getIncludedFiles();
      var flowFileNamesToDeploy = Array.prototype.map.call(srcFiles, function(filename) {
         var orgFlow = new File('./build/flowDefinitions/', filename);
         var gitFlow = new File('./src/flowDefinitions/', filename);
         var gitVersion = getFlowVersion(gitFlow);
         if (!orgFlow.exists()) {
            return flowName(filename, gitVersion);
         }
         var orgVersion = getFlowVersion(orgFlow);
         if (gitVersion > orgVersion) {
            return flowName(filename, gitVersion);
         }
         return null;
      })
         .filter(function(filename) {
            return !!filename;
         });
      flowFileNamesToDeploy.forEach(addFileToFileset.bind(null, flowsToDeploy));

      var flowDefinitionFileNamesToDeploy = Array.prototype.filter.call(srcFiles, function(filename) {
        var orgFlow = new File('./build/flowDefinitions/', filename);
        // Flow Definitions cannot be created by a deploy
        if (! orgFlow.exists()) {
          java.lang.System.out.println('Not going to deploy new flow definition: ' + filename);
        }
        return orgFlow.exists();
      });
      flowDefinitionFileNamesToDeploy.forEach(addFileToFileset.bind(null, flowDefinitions));

      // Overwrite fetched package.xml
      copyFiles('src/package.xml', 'build');
      if (!flowDefinitions.hasPatterns()) {
        java.lang.System.out.println('No Flow Definitions to deploy');
      } else {
        copyFiles(flowDefinitions, 'build/flowDefinitions');
      }

      if (!flowsToDeploy.hasPatterns()) {
        java.lang.System.out.println('No Flows to deploy');
      } else {
        copyFiles(flowsToDeploy, 'build/flows');
      }
   ]]></script>
</target>

Here's the flowDefinitions.xml file used by the retrieveCurrentFlows target to retrieve the current flow definitions:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
   <types>
      <members>*</members>
      <name>FlowDefinition</name>
   </types>
   <version>36.0</version>
</Package>