@interaktiv/dia-scripts
Version:
CLI toolbox with common scripts for most sort of projects at DIA
202 lines (165 loc) • 5.46 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
const path = require('path');
const chalk = require('chalk');
const chokidar = require('chokidar');
const fs = require('fs-extra');
const ignore = require('ignore');
const uuid = require('uuid');
const {
fromRoot
} = require('../../utils');
const {
runDeploy
} = require('./runDeploy');
const runCssCompile = require('./runCssCompile');
const DEPLOY_IGNORE = fromRoot('.deployignore');
const DEFAULT_SRC_DIR = fromRoot('force-app/main/default/');
/**
* Object that queues the deployment tasks
*
* @type {Object}
*/
const deploymentQueue = {
list: [],
add(task) {
this.list = [...this.list, {
task,
uuid: uuid.v1()
}];
this.walk();
},
walk() {
const items = this.list.filter(item => typeof item.task === 'function');
const next = () => {
const item = items.pop();
const itemIndex = this.list.findIndex(itm => itm.uuid === item.uuid);
if (itemIndex > -1) {
this.list = [...this.list.slice(0, itemIndex), ...this.list.slice(itemIndex + 1)];
}
item.task(item.uuid);
if (items.length) next();
};
next();
}
}; // eslint-disable-next-line max-lines-per-function
module.exports = function ({
argv
}) {
const sourceDir = argv.sourceDir || DEFAULT_SRC_DIR;
/**
* Watcher function for scss files. Transpiles them to css files
*
* @private
* @param {string} file The path to the changed file
*/
const deploymentWrapper = filename => {
return (file => {
return async () => {
const result = await runDeploy({
argv: (0, _extends2.default)({}, argv, {
_: [...argv._, path.join(sourceDir, file)]
})
});
if (result.status === 0) {
console.log(chalk.green('Deployment successful ✅'));
} else {
console.error(chalk.red('Deployment failed ❌'));
}
};
})(filename);
};
/**
* Tries to deploy a file through sfdx cli
*
* @private
* @param {String} filename The file path to be deployed
*/
const deploy = filename => {
deploymentQueue.add(deploymentWrapper(filename));
};
/**
* Default file handler. Directly deploys file.
*
* @private
* @param {string} file The path to the file that changed
*/
// Mapping of file extensions related to handler funcs
// Can be files, dir, array or glob
const deploymentHookMap = {
'**/*.scss': async file => {
console.info(chalk.cyan('Compiling SCSS'));
try {
// Deployment should be done via the other file watcher that is watching
// for `.css` file changes too
await runCssCompile({
argv,
file: path.join(sourceDir, file)
});
} catch (error) {
console.log(chalk.red(`SASS Error:\n${error.formatted || error}`));
}
},
'**/*.+(js|css|html|xml|cls|class|trg|trigger|apx|apex)': file => {
deploy(file);
}
};
/**
* Handles watcher event.
* Calls discrete handler for file types.
*
* If file type is not supported, show error message.
*
* @private
* @param {Object} hook={} The event of the watcher
* @param {Function} hook.handler The handler to execute on file change
* @param {string} hook.paths The file / dir / glob to watch for changes
* @param {string} file The path of the file that changed
* @param {Object<Ignore>} ignoreInstance An instance of ignore()
*/
// eslint-disable-next-line default-param-last
const watcherFassade = ({
handler,
paths
} = {}, file, ignoreInstance) => {
// Show info message
console.info(`\n${chalk.blue('File changed:')} ${file} ℹ️`);
if (typeof handler !== 'function') {
console.warn(chalk.yellow(`\nNo handler found for "${paths}", skipping file "${file}"`));
return;
}
if (ignoreInstance && ignoreInstance.ignores(path.join(file))) {
console.info(`\n${chalk.blue('File ignored:')} ${file} ⚠️`);
return;
}
handler(file);
}; // Register watchers
let watcherList = [];
const readyQueue = Object.keys(deploymentHookMap) // Remove falsey keys
.filter(Boolean) // Remove falsey values
.filter(paths => deploymentHookMap[paths]) // Convert to hook config obj
.map(paths => ({
paths,
handler: deploymentHookMap[paths]
})) // Instantiate watcher and add listener
.map(hook => new Promise((resolve, reject) => {
const ignoreInstance = ignore();
if (fs.existsSync(DEPLOY_IGNORE)) {
ignoreInstance.add(fs.readFileSync(DEPLOY_IGNORE).toString());
}
const watcher = chokidar.watch(hook.paths, {
cwd: sourceDir,
ignoreInitial: true
});
watcherList = [...watcherList, watcher];
watcher.on('ready', () => {
console.info(`\nWatching: ${hook.paths} on ${sourceDir}`);
resolve(watcher);
}).on('error', reject).on('add', filePath => watcherFassade(hook, filePath, ignoreInstance)).on('change', filePath => watcherFassade(hook, filePath, ignoreInstance));
})); // Add cleanup handling on `process.exit()`
process.on('exit', async () => {
await Promise.all([watcherList.filter(Boolean).filter(watcher => typeof watcher.close === 'function').map(watcher => watcher.close())]);
});
return Promise.all(readyQueue);
};