diff --git a/Readme.md b/Readme.md index b3cc6cbc..4990fcc6 100644 --- a/Readme.md +++ b/Readme.md @@ -120,9 +120,17 @@ Create `~/.mrm/config.json` or `~/dotfiles/mrm/config.json`: } ``` +Via [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) `.mrmrc.json`: + +```json5 +{ + "preset": "@company/mrm-preset-default" +} +``` + See [tasks docs](https://github.com/sapegin/mrm-tasks) for available config options. -*Config file isn’t required, you can also pass config options via command line. Default tasks will try to [read data](https://github.com/sapegin/user-meta) fom your npm and Git configuration.* +*Config file isn’t required, you can also pass config options via command line or cosmiconfig. Default tasks will try to [read data](https://github.com/sapegin/user-meta) fom your npm and Git configuration.* ## Tasks diff --git a/bin/mrm.js b/bin/mrm.js index d4e59e50..7afcdff5 100755 --- a/bin/mrm.js +++ b/bin/mrm.js @@ -11,7 +11,7 @@ const listify = require('listify'); const updateNotifier = require('update-notifier'); const { padEnd, sortBy } = require('lodash'); const { random } = require('middleearth-names'); -const { run, getConfig, getAllTasks, tryResolve } = require('../src/index'); +const { run, getConfig, getConfigFromCosmiconfig, getAllTasks, tryResolve } = require('../src/index'); const { MrmUnknownTask, MrmUnknownAlias, MrmUndefinedOption } = require('../src/errors'); let directories = [path.resolve(userHome, 'dotfiles/mrm'), path.resolve(userHome, '.mrm')]; @@ -43,9 +43,13 @@ const tasks = argv._; const binaryPath = process.env._; const binaryName = binaryPath && binaryPath.endsWith('/npx') ? 'npx mrm' : 'mrm'; +const configFromCosmiconfig = getConfigFromCosmiconfig(); + // Custom config / tasks directory -if (argv.dir) { - const dir = path.resolve(argv.dir); +const customDirs = [configFromCosmiconfig, argv] + .filter(conf => conf.dir) + .map(conf => path.resolve(conf.dir)); +for (const dir of customDirs) { if (!isDirectory.sync(dir)) { printError(`Directory “${dir}” not found.`); process.exit(1); @@ -55,7 +59,7 @@ if (argv.dir) { } // Preset -const preset = argv.preset || 'default'; +const preset = argv.preset || configFromCosmiconfig.preset || 'default'; const isDefaultPreset = preset === 'default'; if (isDefaultPreset) { directories.push(path.dirname(require.resolve('mrm-preset-default'))); @@ -70,7 +74,7 @@ We’ve tried to load “mrm-preset-${preset}” and “${preset}” globally in directories = [path.dirname(presetPath)]; } -const options = getConfig(directories, 'config.json', argv); +const options = getConfig(directories, 'config.json', argv, configFromCosmiconfig); if (tasks.length === 0 || tasks[0] === 'help') { commandHelp(); } else { diff --git a/package-lock.json b/package-lock.json index c33b77c6..edff633d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1089,10 +1089,9 @@ "dev": true }, "cosmiconfig": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", - "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", - "dev": true, + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", + "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", "requires": { "is-directory": "^0.3.1", "js-yaml": "^3.9.0", @@ -1103,7 +1102,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -1394,7 +1392,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -2910,8 +2907,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-buffer": { "version": "1.1.6", @@ -3773,8 +3769,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parser": { "version": "1.1.5", diff --git a/package.json b/package.json index 7d81a80d..eed7c6d5 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "src" ], "dependencies": { + "cosmiconfig": "^5.0.6", "git-username": "^1.0.0", "glob": "^7.1.2", "is-directory": "^0.3.1", diff --git a/src/__tests__/index.spec.js b/src/__tests__/index.spec.js index 17c7bb6f..81ddc217 100644 --- a/src/__tests__/index.spec.js +++ b/src/__tests__/index.spec.js @@ -8,6 +8,7 @@ const { tryResolve, getConfigFromFile, getConfigFromCommandLine, + getConfigFromCosmiconfig, getConfig, getConfigGetter, runTask, @@ -42,6 +43,8 @@ const argv = { 'config:foo': 42, 'config:bar': 'coffee', }; +const cosmiconfigOptions = {stopDir: path.resolve(__dirname, '../..')}; +const cosmiconfigDirectory = path.resolve(__dirname, '../../test/cosmiconfig'); const file = name => path.join(__dirname, '../../test', name); @@ -121,6 +124,21 @@ describe('getConfigFromCommandLine', () => { }); }); +describe('getConfigFromCosmiconfig', () => { + it('should return a config object', () => { + const result = getConfigFromCosmiconfig(cosmiconfigDirectory, cosmiconfigOptions); + expect(result).toEqual({ + foo: 42, + bar: 'coffee', + }); + }); + + it('should return an empty object when no config options found', () => { + const result = getConfigFromCosmiconfig(__dirname, cosmiconfigOptions); + expect(result).toEqual({}); + }); +}); + describe('getConfig', () => { it('should return a config object', () => { const result = getConfig(directories, configFile, argv); diff --git a/src/index.js b/src/index.js index 68d66304..88d3a62d 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const glob = require('glob'); const kleur = require('kleur'); const requireg = require('requireg'); const { get, forEach } = require('lodash'); +const cosmiconfig = require('cosmiconfig'); const { MrmUnknownTask, MrmUnknownAlias, MrmUndefinedOption } = require('./errors'); /* eslint-disable no-console */ @@ -209,12 +210,14 @@ function getConfigGetter(options) { * @param {string[]} directories * @param {string} filename * @param {Object} argv + * @param {Object} [configFromCosmiconfig] * @return {Object} */ -function getConfig(directories, filename, argv) { +function getConfig(directories, filename, argv, configFromCosmiconfig) { return Object.assign( {}, getConfigFromFile(directories, filename), + configFromCosmiconfig || {}, getConfigFromCommandLine(argv) ); } @@ -251,6 +254,24 @@ function getConfigFromCommandLine(argv) { return options; } +/** + * Get config options from cosmiconfig. + * + * @param {string} [searchFrom] + * @param {Object} [cosmiconfigOptions] + * @return {Object} + */ +function getConfigFromCosmiconfig(searchFrom, cosmiconfigOptions) { + const explorer = cosmiconfig('mrm', cosmiconfigOptions); + const result = explorer.searchSync(searchFrom); + + if (result) { + return result.config; + } + + return {}; +} + /** * Try to load a file from a list of folders. * @@ -306,6 +327,7 @@ module.exports = { getConfig, getConfigFromFile, getConfigFromCommandLine, + getConfigFromCosmiconfig, tryFile, tryResolve, firstResult, diff --git a/test/cosmiconfig/.mrmrc.json b/test/cosmiconfig/.mrmrc.json new file mode 100644 index 00000000..607d0fba --- /dev/null +++ b/test/cosmiconfig/.mrmrc.json @@ -0,0 +1 @@ +{"bar": "coffee", "foo": 42}