UNPKG

@interaktiv/dia-scripts

Version:

CLI toolbox with common scripts for most sort of projects at DIA

420 lines (332 loc) 11.6 kB
"use strict"; 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 };