@interaktiv/dia-scripts
Version:
CLI toolbox with common scripts for most sort of projects at DIA
420 lines (332 loc) • 11.6 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
const path = require('path');
const {
ensureArray,
ensureString
} = require('@interaktiv/types');
const fs = require('fs-extra');
const arrify = require('arrify');
const has = require('lodash.has');
const readPkgUp = require('read-pkg-up');
const rimraf = require('rimraf');
const semver = require('semver');
const spawn = require('cross-spawn');
const which = require('which');
const hostedGitInfo = require('hosted-git-info');
const APEX_EXTENSION_REGEX = /\.cl(as)?s$|\.trg$|\.trigger$|\.ap(e)?x$/;
const JS_EXTENSION_REGEX = /\.jsx?$|\.tsx?$/;
const MARKDOWN_EXTENSION_REGEX = /\.md$|\.m(ark)?down$/;
const MIN_NODE_VERSION = 8;
const SCRIPTS_PACKAGE_NAME = '@interaktiv/dia-scripts';
const getCwd = () => parseEnv('INIT_CWD', process.cwd()); // If we are running on an npm lifecycle hook like `postinstall`, the
// `process.cwd()` always is the path to the package itself i.e dia-scripts.
// Therefore npm sets an `INIT_CWD` env var so package scripts can handle such
// stuff accordingly in there npm lifecycle hooks. We are using this mechanism
// to determining the right `package.json` file. Means on an npm lifecycle hook
// this should be the one from `INIT_CWD` and for normal CLI usage it should be
// `process.cwd()` dir one
const {
package: pkg,
path: pkgPath
} = readPkgUp.sync({
cwd: fs.realpathSync(getCwd())
});
const appDirectory = path.dirname(pkgPath);
function resolveSelf() {
if (pkg.name === SCRIPTS_PACKAGE_NAME) {
return require.resolve('./').replace(process.cwd(), '.');
}
return resolveBin(SCRIPTS_PACKAGE_NAME, {
executable: 'dia-scripts',
cwd: getCwd()
});
}
function resolveBin(modName, {
executable = modName,
cwd = getCwd()
} = {}) {
let pathFromWhich;
try {
pathFromWhich = fs.realpathSync(which.sync(executable));
if (pathFromWhich && pathFromWhich.includes('.CMD')) return pathFromWhich;
} catch (_error) {// Ignore _error
}
try {
const modPkgPath = require.resolve(`${modName}/package.json`);
const modPkgDir = path.dirname(modPkgPath);
const {
bin
} = require(modPkgPath);
const binPath = typeof bin === 'string' ? bin : bin[executable];
const fullPathToBin = path.join(modPkgDir, binPath);
if (fullPathToBin === pathFromWhich) return executable;
return fullPathToBin.replace(cwd, '.');
} catch (error) {
if (pathFromWhich) return executable;
throw error;
}
}
const fromRoot = (...p) => path.join(appDirectory, ...p);
const hasFile = (...p) => fs.existsSync(fromRoot(...p));
const ifFile = (files, t, f) => arrify(files).some(file => hasFile(file)) ? t : f;
const hasPkgProp = props => arrify(props).some(prop => has(pkg, prop));
const hasPkgSubProp = pkgProp => props => hasPkgProp(arrify(props).map(p => `${pkgProp}.${p}`));
const ifPkgSubProp = pkgProp => (props, t, f) => hasPkgSubProp(pkgProp)(props) ? t : f;
const hasScript = hasPkgSubProp('scripts');
const hasPeerDep = hasPkgSubProp('peerDependencies');
const hasDep = hasPkgSubProp('dependencies');
const hasDevDep = hasPkgSubProp('devDependencies');
const hasConfig = hasPkgSubProp('config');
const hasAnyDep = args => [hasDep, hasDevDep, hasPeerDep].some(fn => fn(args));
const ifPeerDep = ifPkgSubProp('peerDependencies');
const ifDep = ifPkgSubProp('dependencies');
const ifDevDep = ifPkgSubProp('devDependencies');
const ifAnyDep = (deps, t, f) => hasAnyDep(arrify(deps)) ? t : f;
const ifScript = ifPkgSubProp('scripts');
const ifConfig = ifPkgSubProp('config');
function parseEnv(name, def) {
if (envIsSet(name)) {
try {
return JSON.parse(process.env[name]);
} catch (err) {
return process.env[name];
}
}
return def;
}
function envIsSet(name) {
return Object.prototype.hasOwnProperty.call(process.env, name) && process.env[name] && process.env[name] !== 'undefined';
}
function getConcurrentlyArgs(scripts, {
killOthers = true,
showPrefix = true,
handleInput = true
} = {}) {
const colors = ['bgBlue', 'bgGreen', 'bgMagenta', 'bgCyan', 'bgWhite', 'bgRed', 'bgBlack', 'bgYellow'];
const scriptsToRun = Object.entries(scripts).reduce((all, [name, script]) => script ? (0, _extends2.default)({}, all, {
[name]: script
}) : all, {});
const prefixColors = Object.keys(scriptsToRun).reduce((pColors, _s, i) => pColors.concat([`${colors[i % colors.length]}.bold.reset`]), []).join(',');
const prefix = showPrefix ? ['--prefix', '[{name}]'] : [];
const names = showPrefix ? ['--names', Object.keys(scriptsToRun).join(',')] : []; // prettier-ignore
return [killOthers ? '--kill-others-on-fail' : null, handleInput ? '--handle-input' : null, showPrefix ? null : '--raw', ...prefix, ...names, '--prefix-colors', prefixColors, // Stringify escapes quotes ✨
...Object.values(scriptsToRun).map(s => JSON.stringify(s))].filter(Boolean);
}
function isOptedOut(key, t = true, f = false) {
if (!fs.existsSync(fromRoot('.opt-out'))) {
return f;
}
const contents = fs.readFileSync(fromRoot('.opt-out'), 'utf-8');
return contents.includes(key) ? t : f;
}
function isOptedIn(key, t = true, f = false) {
if (fs.existsSync(fromRoot('.opt-in')) === false) return f;
const contents = fs.readFileSync(fromRoot('.opt-in'), 'utf-8');
return contents.includes(key) ? t : f;
}
function getSupportedNodeVersion({
engines: {
node: nodeVersion = '8'
} = {}
}) {
return semver.coerce(nodeVersion).version;
}
const CACHE_DIR = fromRoot('.cache');
const DIST_DIR = fromRoot('dist');
const LOG_DIR = fromRoot('logs');
const PMD_DIR = fromRoot('.pmd');
const TMP_DIR = fromRoot('tmp');
const TMP_SFDX_DIR = path.join(TMP_DIR, 'dx');
const PMD_CACHE_FILE = path.join(CACHE_DIR, 'pmd.cache');
const PMD_META_DATA_BUNDLE = envIsSet('BUILD_NUMBER') ? path.join(DIST_DIR, `src-${parseEnv('BUILD_NUMBER')}`) : path.join(CACHE_DIR, 'metadata-bundle');
const PMD_IGNORE_FILE = fromRoot('.pmdignore');
const PMD_LOGFILE = path.join(LOG_DIR, 'pmd.log');
const isWindows = () => process.platform === 'win32';
const resolvePmdBin = () => {
return path.resolve(PMD_DIR, 'bin', isWindows() ? 'pmd.bat pmd' : 'run.sh pmd');
};
function cleanDir(dir) {
if (dir) rimraf.sync(dir);
}
function createPmdMetaDataBundleFromFiles(files, clean = true) {
ensureArray(files);
return createPmdMetaDataBundle(files, clean, false);
}
function createPmdMetaDataBundleFromDir(sourceDir, clean = true) {
ensureString(sourceDir);
return createPmdMetaDataBundle(sourceDir, clean, true);
}
function createPmdMetaDataBundle(sources, clean, fromDir) {
if (clean) cleanDir(PMD_META_DATA_BUNDLE);
let toConvert;
if (fromDir === true) {
toConvert = ['--rootdir', sources];
} else {
toConvert = ['--sourcepath', sources.join(',')];
}
const result = spawn.sync(resolveBin('sfdx'), ['force:source:convert', ...toConvert, ...['--outputdir', PMD_META_DATA_BUNDLE]], {
stdio: 'inherit'
});
return result.status;
}
function createFileIfNeeded(filePath) {
// Create dirs and file recursively
fs.ensureFileSync(filePath);
}
const isSfdxProject = () => hasFile('sfdx-project.json');
const ifSfdxProject = (t, f) => isSfdxProject() ? t : f;
const isTitaniumProject = () => ['tiapp-cfg.json', 'tiapp.tpl', 'tiapp.xml'].some(file => hasFile(file));
const ifTitaniumProject = (t, f) => isTitaniumProject() ? t : f;
const isMobileProject = () => isTitaniumProject() || hasAnyDep('react-native');
const ifMobileProject = (t, f) => isMobileProject() ? t : f;
const isJsFile = filePath => filePath ? JS_EXTENSION_REGEX.test(filePath) : false;
const isApexFile = filePath => filePath ? APEX_EXTENSION_REGEX.test(filePath) : false;
const isMarkdownFile = filePath => filePath ? MARKDOWN_EXTENSION_REGEX.test(filePath) : false;
const getGitOriginUrl = (spawnOpts = {}) => {
const gitResult = spawn.sync('git', ['config', '--get', 'remote.origin.url'], spawnOpts);
return ''.concat(gitResult.stdout).trim();
};
const getRepoUrl = () => hasPkgSubProp('repository')('url') ? pkg.repository.url : getGitOriginUrl({
cwd: process.cwd(),
env: process.env
});
const isBitbucketRepo = () => {
const gitInfo = hostedGitInfo.fromUrl(getRepoUrl());
if (gitInfo) return gitInfo.type === 'bitbucket';
return false;
};
const ifBitbucketRepo = (t, f) => isBitbucketRepo() ? t : f;
const isGithubRepo = () => {
const gitInfo = hostedGitInfo.fromUrl(getRepoUrl());
if (gitInfo) return gitInfo.type === 'github';
return false;
};
const ifGithubRepo = (t, f) => isGithubRepo() ? t : f;
const isCommerceProject = () => hasFile('shopware.php');
const ifCommerceProject = (t, f) => isCommerceProject() ? t : f;
const getRunAllArgs = scripts => Object.entries(scripts).reduce((all, [name, script]) => {
if (script == null) return all;
const [task, ...args] = script.split(/\s+/g);
return [...all, {
name,
task,
args: args || []
}];
}, []);
const runAll = function (commands, {
killOthers = true,
showPrefix = true
} = {}) {
const runNext = () => {
const {
name,
task,
args
} = commands.shift();
if (showPrefix) {
console.log();
console.log(`Running task [${name}]`);
}
const {
status: code
} = spawn.sync(task, args, {
stdio: 'inherit'
});
if (code > 0 && killOthers) {
const error = new Error(`[${name}] ${task} ${args.join(' ')} exited with code ${code}`);
error.code = code;
error.status = error.code;
error.cmd = task;
throw error;
}
if (commands.length) runNext();
};
runNext();
};
const ifCI = (t, f) => require('is-ci') ? t : f;
const removeFromArray = (arr, func) => Array.isArray(arr) ? arr.filter(func).reduce((acc, val) => {
arr.splice(arr.indexOf(val), 1);
return acc.concat(val);
}, []) : [];
const removeValueFromArray = (arr, val) => removeFromArray(arr, v => v !== val);
const matchArg = (args, operand) => {
const eq = operand instanceof RegExp ? v => operand.test(v) : v => v === operand;
let match = false;
args.every(arg => !(match = eq(arg)));
return match;
};
const omit = function (obj, arr = []) {
return Object.keys(obj).filter(key => !arr.includes(key)).reduce((acc, key) => (0, _extends2.default)({}, acc, {
[key]: obj[key]
}), {});
};
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const lift2 = f => g => h => x => f(g(x))(h(x));
module.exports = {
MIN_NODE_VERSION,
PMD_CACHE_FILE,
PMD_DIR,
PMD_IGNORE_FILE,
PMD_LOGFILE,
PMD_META_DATA_BUNDLE,
SCRIPTS_PACKAGE_NAME,
TMP_DIR,
TMP_SFDX_DIR,
appDirectory,
cleanDir,
envIsSet,
fromRoot,
getConcurrentlyArgs,
hasFile,
hasPkgProp,
hasScript,
hasConfig,
ifAnyDep,
ifConfig,
ifDep,
ifDevDep,
ifFile,
ifPeerDep,
ifScript,
isOptedIn,
isOptedOut,
omit,
parseEnv,
pkg,
resolveBin,
resolveSelf,
resolvePmdBin,
createPmdMetaDataBundle,
createPmdMetaDataBundleFromFiles,
createPmdMetaDataBundleFromDir,
createFileIfNeeded,
isWindows,
isSfdxProject,
ifSfdxProject,
isTitaniumProject,
ifTitaniumProject,
isJsFile,
isApexFile,
isMarkdownFile,
getSupportedNodeVersion,
isBitbucketRepo,
ifBitbucketRepo,
isGithubRepo,
ifGithubRepo,
isMobileProject,
ifMobileProject,
isCommerceProject,
ifCommerceProject,
getRunAllArgs,
runAll,
ifCI,
removeValueFromArray,
matchArg,
pipe,
compose,
lift2,
getGitOriginUrl
};