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>