1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | 9x 9x 1x 1x 9x 3x 2x 1x 1x 12x 3x 9x 9x 2x 7x 7x 3x 3x 7x 7x 7x 2x 5x 7x 4x 4x 3x 3x 2x 2x 1x 1x 3x 3x 2x 1x 1x 1x 13x 18x 13x 13x 2x 11x 11x 2x 11x 9x 1x 8x 29x 29x 29x 29x 29x 9x 29x 17x 29x 20x 29x 5x 5x 28x 5x 3x 3x 3x 3x 2x 4x 4x 2x 2x 27x 27x 22x 27x 64x 64x 32x 32x 32x 32x 28x 28x 32x 22x 10x | import {resolve} from 'path' import {readFileSync} from 'fs' import { includes, isPlainObject, isUndefined, isEmpty, isFunction, isNull, } from 'lodash' import typeOf from 'type-detect' import chalk from 'chalk' import {safeLoad} from 'js-yaml' import {oneLine} from 'common-tags' import getLogger from '../get-logger' import {resolveScriptObjectToScript} from '../resolve-script-object-to-string' import getScriptByPrefix from './get-script-by-prefix' import initialize from './initialize' const log = getLogger() /** * Attempts to load the given module. This is used for the * --require functionality of the CLI * @param {String} moduleName The module to attempt to require * @return {*} The required module */ const preloadModule = getAttemptModuleRequireFn((moduleName, requirePath) => { log.warn({ message: chalk.yellow( oneLine` Unable to preload "${moduleName}". Attempted to require as "${requirePath}" `, ), ref: 'unable-to-preload-module', }) return undefined }) const loadJSConfig = getAttemptModuleRequireFn(function onFail( configPath, requirePath, err, ) { if (err) { throw err } log.error({ message: chalk.red( oneLine` Unable to find JS config at "${configPath}". `, ), ref: 'unable-to-find-config', }) return undefined }) /** * Attempts to load the config and logs an error if there's a problem * @param {String} configPath The path to attempt to require the config from * @param {*} input the input to pass to the config if it's a function * @return {Object} The config */ // eslint-disable-next-line complexity function loadConfig(configPath, input) { let config if (configPath.endsWith('.yml') || configPath.endsWith('.yaml')) { config = loadYAMLConfig(configPath) } else { config = loadJSConfig(configPath) } if (isUndefined(config)) { // let the caller deal with this return config } let typeMessage = `Your config data type was` if (isFunction(config)) { config = config(input) typeMessage = `${typeMessage} a function which returned` } const emptyConfig = isEmpty(config) const plainObjectConfig = isPlainObject(config) if (plainObjectConfig && emptyConfig) { typeMessage = `${typeMessage} an object, but it was empty` } else { typeMessage = `${typeMessage} a data type of "${typeOf(config)}"` } if (!plainObjectConfig || emptyConfig) { log.error({ message: chalk.red( oneLine` The package-scripts configuration ("${configPath.replace(/\\/g, '/')}") must be a non-empty object or a function that returns a non-empty object. `, ), ref: 'config-must-be-an-object', }) throw new Error(typeMessage) } const defaultConfig = { options: { 'help-style': 'all', }, } return {...defaultConfig, ...config} } function loadCLIConfig(configPath) { try { const {config, require} = JSON.parse(readFileSync(configPath)) return {config, require} } catch (err) { throw new Error( `Failed to parse CLI configuration file: ${configPath}`, err, ) } } export { initialize, help, getModuleRequirePath, preloadModule, loadConfig, loadCLIConfig, specificHelpScript, } /****** implementations ******/ function loadYAMLConfig(configPath) { try { return safeLoad(readFileSync(configPath, 'utf8')) } catch (e) { if (e.constructor.name === 'YAMLException') { throw e } log.error({ message: chalk.red(`Unable to find YML config at "${configPath}".`), ref: 'unable-to-find-config', }) return undefined } } /** * Determines the proper require path for a module. * If the path starts with `.` then it is resolved with process.cwd() * @param {String} moduleName The module path * @return {String} the module path to require */ function getModuleRequirePath(moduleName) { return moduleName[0] === '.' ? require.resolve(resolve(process.cwd(), moduleName)) : moduleName } function getAttemptModuleRequireFn(onFail) { return function attemptModuleRequire(moduleName) { let requirePath try { requirePath = getModuleRequirePath(moduleName) } catch (e) { return onFail(moduleName) } try { return requireDefaultFromModule(requirePath) } catch (e) { return onFail(moduleName, requirePath, e) } } } /** * Requires the given module and returns the `default` if it's an `__esModule` * @param {String} modulePath The module to require * @return {*} The required module (or it's `default` if it's an `__esModule`) */ function requireDefaultFromModule(modulePath) { /* eslint global-require:0,import/no-dynamic-require:0 */ const mod = require(modulePath) if (mod.__esModule) { return mod.default } else { return mod } } function scriptObjectToChalk(options, {name, description, script}) { const coloredName = chalk.green(name) const coloredScript = chalk.gray(script) const line = [coloredName] let showScript = true if (typeof options !== 'undefined' && options['help-style'] === 'basic') { showScript = false } if (description) { line.push(chalk.white(description)) } if (showScript) { line.push(coloredScript) } return line.join(' - ').trim() } function help({scripts, options}) { const availableScripts = getAvailableScripts(scripts) const filteredScripts = availableScripts.filter( script => !script.hiddenFromHelp, ) if (filteredScripts.length > 0) { const scriptLines = filteredScripts.map( scriptObjectToChalk.bind(null, options || {'help-style': 'all'}), ) const topMessage = 'Available scripts (camel or kebab case accepted)' const message = `${topMessage}\n\n${scriptLines.join('\n')}` return message } else { return chalk.yellow('There are no scripts available') } } function specificHelpScript(config, scriptName) { const script = getScriptByPrefix(config, scriptName) if (isNull(script)) { return chalk.yellow(`Script matching name ${scriptName} was not found.`) } else { return scriptObjectToChalk({'help-style': 'all'}, script) } } function getAvailableScripts(config, prefix = [], rootLevel = true) { const excluded = ['description', 'script'] if (!rootLevel) { excluded.push('default') } return Object.keys(config).reduce((scripts, key) => { const val = config[key] if (includes(excluded, key)) { return scripts } const scriptObj = resolveScriptObjectToScript(val) const prefixed = [...prefix, key] if (scriptObj) { const {description, script, hiddenFromHelp = false} = scriptObj scripts = [ ...scripts, {name: prefixed.join('.'), description, script, hiddenFromHelp}, ] } if (isPlainObject(val)) { return [...scripts, ...getAvailableScripts(val, prefixed, false)] } return scripts }, []) } |