sfdx
Version:
Performs Salesforce DX operations with ease! Life is good.
246 lines (213 loc) • 7.69 kB
JavaScript
const config = require('../config/config')
const yargsBuilder = require('../lib/yargsBuilder')
const err = require('../helpers/errorOutput')
const getResults = require('../helpers/compileResults')
const fs = require('fs')
const inquirer = require('inquirer')
const joinPath = require('path').join
const shell = require('shelljs')
const xml2js = require('xml2js')
// Set global variables
const dxSourceDir = config.dxSourceDir
const defPath = joinPath(dxSourceDir, 'flowDefinitions')
const flowPath = joinPath(dxSourceDir, 'flows')
module.exports = {
desc: 'Deploys the flows to an org, ensuring that only one version is deployed and active.',
command: ['deployflows [orgname] [alias|org|a] [forcedelete|fd]'],
aliases: ['flows'],
builder: yargs => {
yargs = yargsBuilder.builder(yargs)
yargs
.positional('orgname', {
describe: 'Alias of the org of which to deploy flows to'
})
.option('alias', {
alias: ['org', 'a'],
describe: 'Alias of the org of which to deploy flows to'
})
.option('forcedelete', {
alias: ['fd'],
describe: 'Attempt to force delete any flow versions that are out-of-date',
type: 'boolean'
})
.option('forcepush', {
alias: ['fp'],
describe: 'Attempt to force overwrite remote code with local changes',
type: 'boolean'
})
.example('$0 deployflows', '- Deploys updated flow versions to default scratch org')
.example(
'$0 flows -a FlowTest --fd',
"- Deploys updated flow versions to org named 'FlowTest', forcibly deleting out-of-date flows"
)
.example('$0 flows -a FlowTest --fp', "- Forcibly deploys updated flow versions to org named 'FlowTest'")
},
handler: argv => {
argv = yargsBuilder.handler(argv)
argv.alias = argv.alias || argv.orgname
let numResults = 0
let results = []
/* (Step 1) Push code to org first, to ensure correct flow version is active */
if (!argv.quiet) {
if (!argv.fromPush) console.log('Beginning code push...')
console.log()
}
results[numResults++] = pushCode(argv)
/* (Step 2) Next, delete any non-active flow definition files */
results[numResults++] = deleteFlows(argv)
return getResults(results)
}
}
const deleteFlows = async argv => {
let numResults = 0
const results = []
const filesToDelete = await getfilesToDelete(argv)
if (filesToDelete.length > 0) {
// Delete files that have been marked to delete
for (const file of filesToDelete) {
const fullPath = joinPath(flowPath, file)
if (!argv.quiet) console.log("Deleting flow '" + file + "'...")
fs.unlinkSync(fullPath)
}
/* (Step 3) Last step is to push again if needed */
if (!argv.quiet) {
console.log('Pushing updated flow versions to ' + (argv.alias ? "'" + argv.alias + "'" : 'default org') + '...')
console.log()
}
results[numResults++] = pushCode(argv)
} else {
const output = 'All flows up to date!'
if (!argv.quiet) console.log(output)
results[numResults++] = {}
results[numResults - 1].stdout = output
}
return getResults(results)
}
const getfilesToDelete = async argv => {
if (!argv.quiet) {
console.log()
console.log('Checking for out-of-date flows...')
}
// Get list of files that define each flow and flow version
const questions = []
const filesToDelete = []
// For the version number, look for the number at the end of the filename, after the '-', and before the extension ('.')
const regexVersion = /(?!-)[0-9]+(?=\.)/
// Attempt to check flow directories
let defFileList = []
let flowFileList = []
if (fs.existsSync(defPath)) {
try {
defFileList = await fs.readdirSync(defPath)
// Get list of flows ordered by version number
flowFileList = await fs
.readdirSync(flowPath)
.map(file => {
return {
name: file,
time: fs.statSync(joinPath(flowPath, file)).mtime.getTime(),
version: parseInt(regexVersion.exec(file)[0])
}
})
.sort((a, b) => {
return a.version - b.version
})
.map(file => {
return file.name
})
} catch (fileError) {
let errorMsg = config.stars + 'ERROR:' + config.stars
errorMsg += '\n' + fileError
if (!argv.quiet) console.error(fileError)
return { stderr: errorMsg }
}
} else {
// No flow directory
return {}
}
const flowDefs = []
const regexDefFilename = /.+(?=\.flowDefinition-meta\.xml)/
for (const defFile of defFileList) {
const defFilename = regexDefFilename.exec(defFile)[0]
// Get active version number from definition files
const parser = new xml2js.Parser()
const fileData = await fs.readFileSync(joinPath(defPath, defFile))
parser.parseString(fileData, (err, result) => {
const defVersion = parseInt(result['FlowDefinition']['activeVersionNumber'])
flowDefs[defFilename] = { activeVersion: defVersion }
})
}
// For the filename, look at everything until '-' followed by a number
const regexFlowFilename = /.+?(?=-[0-9]+\.)/
let flowFile = ''
let flowsToKeep = config.inactiveFlowsToKeep
if (isNaN(flowsToKeep)) {
console.error(err('Invalid number of flows specified in configuration.'))
process.exit(1)
}
for (let flowNum = flowFileList.length - 1; flowNum >= 0; flowNum--) {
flowFile = flowFileList[flowNum]
// Skip hidden files
if (flowFile.startsWith('.')) continue
const flowFilename = regexFlowFilename.exec(flowFile)[0]
const flowVersion = parseInt(regexVersion.exec(flowFile)[0])
if (flowDefs[flowFilename].activeVersion !== flowVersion) {
if (argv.forcedelete) {
if (flowDefs[flowFilename].activeVersion < flowVersion) {
console.warn(
config.stars+ 'WARNING: Active flow version is less than latest flow file version.' + config.stars
)
console.warn(
config.stars +
"Please either update the active flow version, run the 'deployflows' command without the '--forcedelete' option, or delete the file(s)"
)
process.exit(flowVersion)
}
if (flowsToKeep > 0) {
flowsToKeep -= 1
} else {
const toDelete = flowFile
filesToDelete.push(toDelete)
}
} else {
if (flowsToKeep > 0) {
flowsToKeep -= 1
} else {
questions.push({
default: false,
message: 'Delete ' + flowFile + '?',
name: flowFilename + '-' + flowVersion,
type: 'confirm'
})
}
}
}
}
if (!argv.forcedelete) {
const answers = await inquirer.prompt(questions)
for (let toDelete in answers) {
if (answers[toDelete]) filesToDelete.push(toDelete + '.flow-meta.xml')
}
}
return filesToDelete
}
const pushCode = argv => {
let pushCommand = 'sfdx force:source:push'
if (argv.forcepush) pushCommand += ' --forceoverwrite'
if (argv.alias) pushCommand += ' --targetusername ' + argv.alias
if (argv.json) pushCommand += ' --json'
const result = shell.exec(pushCommand)
if (result.stderr || result.stdout.indexOf('ERROR') != -1) {
if (!argv.quiet) console.log()
// Check if the error is due to flows
if (result.stdout.indexOf('flow') != -1) {
console.error(
err('Could not deploy code. Check that the active flow versions have not been modified or deleted.')
)
} else {
console.error(err('Could not deploy code.'))
}
process.exit(1)
}
return result
}