Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Configuration via cosmiconfig #34

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 13 additions & 7 deletions bin/mrm.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ 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')];
const directories = [path.resolve(userHome, 'dotfiles/mrm'), path.resolve(userHome, '.mrm')];

const EXAMPLES = [
['', '', 'List of available tasks'],
Expand Down Expand Up @@ -43,9 +43,11 @@ const tasks = argv._;
const binaryPath = process.env._;
const binaryName = binaryPath && binaryPath.endsWith('/npx') ? 'npx mrm' : 'mrm';

let options = getConfig(directories, 'config.json', argv);

// Custom config / tasks directory
if (argv.dir) {
const dir = path.resolve(argv.dir);
if (options.dir) {
const dir = path.resolve(options.dir);
if (!isDirectory.sync(dir)) {
printError(`Directory “${dir}” not found.`);
process.exit(1);
Expand All @@ -55,7 +57,7 @@ if (argv.dir) {
}

// Preset
const preset = argv.preset || 'default';
const preset = options.preset || 'default';
const isDefaultPreset = preset === 'default';
if (isDefaultPreset) {
directories.push(path.dirname(require.resolve('mrm-preset-default')));
Expand All @@ -67,10 +69,14 @@ if (isDefaultPreset) {
We’ve tried to load “mrm-preset-${preset}” and “${preset}” globally installed npm packages.`);
process.exit(1);
}
directories = [path.dirname(presetPath)];
const presetDir = path.dirname(presetPath);
const presetConfig = getConfigFromCosmiconfig(presetDir, {
searchPlaces: ['config.json']
});
directories.push(presetDir);
options = Object.assign({}, presetConfig, options);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clearly code, but useful for shareable configs via presets, like aliases or company based configs.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this code is doing, could you please add comment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shareable configs via preset useful for teams, sample <preset>/config.json:

{
	"emailValidator": "([\\w\\.]+)@corp\\.mail\\.ru",
	"any-shareable-config": "some-value",
	"aliases": {
		"component": ["license", "readme", "editorconfig", "gitignore", "husky"]
	}
}

as default config for task, that can rewrite by developer config, if needed.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some docs for this use case?

}

const options = getConfig(directories, 'config.json', argv);
if (tasks.length === 0 || tasks[0] === 'help') {
commandHelp();
} else {
Expand Down
15 changes: 5 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"src"
],
"dependencies": {
"cosmiconfig": "^5.0.6",
"git-username": "^1.0.0",
"glob": "^7.1.2",
"is-directory": "^0.3.1",
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
tryResolve,
getConfigFromFile,
getConfigFromCommandLine,
getConfigFromCosmiconfig,
getConfig,
getConfigGetter,
runTask,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -215,6 +216,7 @@ function getConfig(directories, filename, argv) {
return Object.assign(
{},
getConfigFromFile(directories, filename),
getConfigFromCosmiconfig(),
getConfigFromCommandLine(argv)
);
}
Expand Down Expand Up @@ -251,6 +253,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.
*
Expand Down Expand Up @@ -306,6 +326,7 @@ module.exports = {
getConfig,
getConfigFromFile,
getConfigFromCommandLine,
getConfigFromCosmiconfig,
tryFile,
tryResolve,
firstResult,
Expand Down
1 change: 1 addition & 0 deletions test/cosmiconfig/.mrmrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"bar": "coffee", "foo": 42}