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

User-configurable default preset #186

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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: 10 additions & 0 deletions docs/Making_presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,13 @@ The package name should should follow this pattern: `mrm-preset-<TASK>`, otherwi
mrm license --preset unicorn # mrm-preset-unicorn
mrm license --preset @mycompany/unicorn-preset # @mycompany/unicorn-preset
```

## Default preset

The default preset can be configured in `.mrm/config.json`:

```json
{
"preset": "@mycompany/unicorn"
}
```
9 changes: 7 additions & 2 deletions packages/mrm/bin/mrm.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { random } = require('middleearth-names');
const {
run,
getConfig,
getConfigFromFile,
getAllTasks,
resolveUsingNpx,
getPackageName,
Expand Down Expand Up @@ -64,15 +65,19 @@ async function main() {
const binaryName =
binaryPath && binaryPath.endsWith('/npx') ? 'npx mrm' : 'mrm';

// Load user configuration from default directories first (e.g. `~/.mrm/config.json`).
// This does not include configuration from command line arguments, presets or the local project.
const userConfig = await getConfigFromFile(defaultDirectories, 'config.json');

// Preset
const preset = argv.preset || 'default';
const preset = argv.preset || userConfig.preset || 'default';
const isDefaultPreset = preset === 'default';
const directories = await resolveDirectories(defaultDirectories);
const options = await getConfig(directories, 'config.json', argv);
if (tasks.length === 0 || tasks[0] === 'help') {
commandHelp();
} else {
run(tasks, directories, options, argv).catch(err => {
run(tasks, directories, preset, options, argv).catch(err => {
if (err.constructor === MrmUnknownAlias) {
printError(err.message);
} else if (err.constructor === MrmUnknownTask) {
Expand Down
113 changes: 94 additions & 19 deletions packages/mrm/src/__tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable no-console */

const path = require('path');
const kleur = require('kleur');
const {
promiseFirst,
tryFile,
Expand All @@ -28,6 +29,7 @@ const task6 = require('../../test/dir3/task6');
const task8 = require('../../test/dir5/task8');

const configFile = 'config.json';
const defaultPreset = 'default';
const directories = [
path.resolve(__dirname, '../../test/dir1'),
path.resolve(__dirname, '../../test/dir2'),
Expand All @@ -53,6 +55,9 @@ const argv = {

const file = name => path.join(__dirname, '../../test', name);

// Disable color output for output generated by test-cases
kleur.enabled = false;

describe('promiseFirst', () => {
it('should return the first resolving function', async () => {
const result = await promiseFirst([
Expand Down Expand Up @@ -311,7 +316,7 @@ describe('runTask', () => {

it('should run a module', () => {
return new Promise((resolve, reject) => {
runTask('task1', directories, {}, {})
runTask('task1', directories, defaultPreset, {}, {})
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
resolve();
Expand All @@ -322,7 +327,9 @@ describe('runTask', () => {

it('should pass a config function and a params object to a module', () => {
return new Promise((resolve, reject) => {
runTask('task1', directories, options, { interactive: false })
runTask('task1', directories, defaultPreset, options, {
interactive: false,
})
.then(() => {
expect(task1).toHaveBeenCalledWith(
expect.objectContaining({ pizza: 'salami' }),
Expand All @@ -335,7 +342,13 @@ describe('runTask', () => {
});

it('should throw when module not found', () => {
const pizza = runTask('this-does-not-exist-on-npm', directories, {}, {});
const pizza = runTask(
'this-does-not-exist-on-npm',
directories,
defaultPreset,
{},
{}
);

// ideally we can use toThrowError but that works with >= jest@22
// https://github.com/facebook/jest/issues/5076
Expand All @@ -346,7 +359,7 @@ describe('runTask', () => {
}, 20000);

it('should throw when task module is invalid', () => {
const result = runTask('task7', directories, {}, {});
const result = runTask('task7', directories, defaultPreset, {}, {});

// ideally we can use toThrowError but that works with >= jest@22
// https://github.com/facebook/jest/issues/5076
Expand All @@ -358,7 +371,7 @@ describe('runTask', () => {

it('should run an async module', () => {
return new Promise((resolve, reject) => {
runTask('task4', directories, {}, { stack: [] })
runTask('task4', directories, defaultPreset, {}, { stack: [] })
.then(() => {
expect(task4).toHaveBeenCalledTimes(1);
expect(task4.mock.calls[0][1].stack).toEqual(['Task 2.4']);
Expand All @@ -374,7 +387,13 @@ describe('runTask', () => {
'second-config': 'other value',
});

await runTask('task6', directories, {}, { interactive: true });
await runTask(
'task6',
directories,
defaultPreset,
{},
{ interactive: true }
);

expect(task6).toHaveBeenCalledTimes(1);
expect(task6).toHaveBeenCalledWith(
Expand All @@ -389,7 +408,13 @@ describe('runTask', () => {
it('should respect config defaults from task parameters when interactive mode is on', async () => {
configureInquirer({ 'first-config': 'value' });

await runTask('task6', directories, {}, { interactive: true });
await runTask(
'task6',
directories,
defaultPreset,
{},
{ interactive: true }
);

expect(task6).toHaveBeenCalledTimes(1);
expect(task6).toHaveBeenCalledWith(
Expand All @@ -402,7 +427,13 @@ describe('runTask', () => {
});

it('should respect config defaults from task parameters when interactive mode is off', async () => {
await runTask('task6', directories, {}, { interactive: false });
await runTask(
'task6',
directories,
defaultPreset,
{},
{ interactive: false }
);

expect(task6).toHaveBeenCalledTimes(1);
expect(task6).toHaveBeenCalledWith(
Expand All @@ -415,9 +446,33 @@ describe('runTask', () => {
});

it('should run normally when interactive mode is on but task has no interactive parameters', async () => {
await runTask('task3', directories, {}, { interactive: true });
await runTask(
'task3',
directories,
defaultPreset,
{},
{ interactive: true }
);
expect(task3).toHaveBeenCalledTimes(1);
});

it('should log when using default preset', async () => {
expect.assertions(1);
const spy = jest.spyOn(console, 'log').mockReturnValue(undefined);
await runTask('task1', directories, defaultPreset, {}, {});
expect(spy).toHaveBeenCalledWith('Running task1...');
spy.mockRestore();
});

it('should log when using custom preset', async () => {
expect.assertions(1);
const spy = jest.spyOn(console, 'log').mockReturnValue(undefined);
await runTask('task1', directories, 'custom-preset', {}, {});
expect(spy).toHaveBeenCalledWith(
'Running task1 (from preset "custom-preset")...'
);
spy.mockRestore();
});
});

describe('runAlias', () => {
Expand All @@ -431,7 +486,9 @@ describe('runAlias', () => {

it('should run all tasks defined in an alias', () => {
return new Promise((resolve, reject) => {
runAlias('alias1', directories, optionsWithAliases, { stack: [] })
runAlias('alias1', directories, defaultPreset, optionsWithAliases, {
stack: [],
})
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
expect(task2).toHaveBeenCalledTimes(0);
Expand All @@ -445,7 +502,13 @@ describe('runAlias', () => {
});

it('should throw when alias not found', () => {
const pizza = runAlias('pizza', directories, optionsWithAliases, {});
const pizza = runAlias(
'pizza',
directories,
defaultPreset,
optionsWithAliases,
{}
);
return expect(pizza).rejects.toHaveProperty(
'message',
'Alias “pizza” not found.'
Expand All @@ -456,7 +519,9 @@ describe('runAlias', () => {
return new Promise((resolve, reject) => {
const stack = [];

runAlias(['alias2'], directories, optionsWithAliases, { stack })
runAlias(['alias2'], directories, defaultPreset, optionsWithAliases, {
stack,
})
.then(() => {
expect(task4).toHaveBeenCalledTimes(1);
expect(task5).toHaveBeenCalledTimes(1);
Expand All @@ -479,7 +544,7 @@ describe('run', () => {

it('should run a task', () => {
return new Promise((resolve, reject) => {
run('task1', directories, optionsWithAliases, {})
run('task1', directories, defaultPreset, optionsWithAliases, {})
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
resolve();
Expand All @@ -490,7 +555,9 @@ describe('run', () => {

it('should run all tasks defined in an alias', () => {
return new Promise((resolve, reject) => {
run('alias1', directories, optionsWithAliases, { stack: [] })
run('alias1', directories, defaultPreset, optionsWithAliases, {
stack: [],
})
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
expect(task2).toHaveBeenCalledTimes(0);
Expand All @@ -505,9 +572,15 @@ describe('run', () => {

it('should run multiple tasks', () => {
return new Promise((resolve, reject) => {
run(['task1', 'task2', 'task4'], directories, optionsWithAliases, {
stack: [],
})
run(
['task1', 'task2', 'task4'],
directories,
defaultPreset,
optionsWithAliases,
{
stack: [],
}
)
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
expect(task2).toHaveBeenCalledTimes(1);
Expand All @@ -522,7 +595,9 @@ describe('run', () => {
it('should run multiple tasks in sequence', () => {
return new Promise((resolve, reject) => {
const stack = [];
run(['task4', 'task5'], directories, optionsWithAliases, { stack })
run(['task4', 'task5'], directories, defaultPreset, optionsWithAliases, {
stack,
})
.then(() => {
expect(task4).toHaveBeenCalledTimes(1);
expect(task5).toHaveBeenCalledTimes(1);
Expand All @@ -535,7 +610,7 @@ describe('run', () => {

it('should run a task in a custom preset', () => {
return new Promise((resolve, reject) => {
run('task1', presetDir, optionsWithAliases, {})
run('task1', presetDir, defaultPreset, optionsWithAliases, {})
.then(() => {
expect(task1).toHaveBeenCalledTimes(1);
resolve();
Expand Down
25 changes: 17 additions & 8 deletions packages/mrm/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,37 +66,39 @@ function promiseSeries(items, iterator) {
*
* @param {string|string[]} name
* @param {string[]} directories
* @param {string} preset
* @param {Object} options
* @param {Object} argv
* @returns {Promise}
*/
function run(name, directories, options, argv) {
function run(name, directories, preset, options, argv) {
if (Array.isArray(name)) {
return new Promise((resolve, reject) => {
promiseSeries(name, n => {
return run(n, directories, options, argv);
return run(n, directories, preset, options, argv);
})
.then(() => resolve())
.catch(reject);
});
}

if (getAllAliases(options)[name]) {
return runAlias(name, directories, options, argv);
return runAlias(name, directories, preset, options, argv);
}
return runTask(name, directories, options, argv);
return runTask(name, directories, preset, options, argv);
}

/**
* Run an alias.
*
* @param {string} aliasName
* @param {string[]} directories
* @param {string} preset
* @param {Object} options
* @param {Object} [argv]
* @returns {Promise}
*/
function runAlias(aliasName, directories, options, argv) {
function runAlias(aliasName, directories, preset, options, argv) {
return new Promise((resolve, reject) => {
const tasks = getAllAliases(options)[aliasName];
if (!tasks) {
Expand All @@ -107,7 +109,7 @@ function runAlias(aliasName, directories, options, argv) {
console.log(kleur.yellow(`Running alias ${aliasName}...`));

promiseSeries(tasks, name => {
return runTask(name, directories, options, argv);
return runTask(name, directories, preset, options, argv);
})
.then(() => resolve())
.catch(reject);
Expand All @@ -133,11 +135,12 @@ function getPackageName(type, packageName) {
*
* @param {string} taskName
* @param {string[]} directories
* @param {string} preset
* @param {Object} options
* @param {Object} [argv]
* @returns {Promise}
*/
async function runTask(taskName, directories, options, argv) {
async function runTask(taskName, directories, preset, options, argv) {
const taskPackageName = getPackageName('task', taskName);
let modulePath;
try {
Expand Down Expand Up @@ -169,7 +172,13 @@ async function runTask(taskName, directories, options, argv) {
return;
}

console.log(kleur.cyan(`Running ${taskName}...`));
if (preset === 'default') {
console.log(kleur.cyan(`Running ${taskName}...`));
} else {
console.log(
kleur.cyan(`Running ${taskName} (from preset "${preset}")...`)
);
}

Promise.resolve(getTaskOptions(module, argv.interactive, options))
.then(config => module(config, argv))
Expand Down