Skip to content

Commit

Permalink
feat: Automatic global npm install for tasks and presets (#92)
Browse files Browse the repository at this point in the history
Mrm will prompt the user to globally install tasks and presets that aren't yet installed but available on npm.

Closes #59.
  • Loading branch information
nperez0111 authored Sep 18, 2020
1 parent fc097b1 commit 87dc649
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 305 deletions.
267 changes: 144 additions & 123 deletions packages/mrm/bin/mrm.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const {
getAllTasks,
tryResolve,
getPackageName,
getGlobalPackageName,
installGlobalPackage,
} = require('../src/index');
const {
MrmUnknownTask,
Expand All @@ -25,7 +27,7 @@ const {
MrmUndefinedOption,
} = require('../src/errors');

let directories = [
const defaultDirectories = [
path.resolve(userHome, 'dotfiles/mrm'),
path.resolve(userHome, '.mrm'),
];
Expand Down Expand Up @@ -55,88 +57,65 @@ process.on('unhandledRejection', err => {
}
});

const argv = minimist(process.argv.slice(2), { alias: { i: 'interactive' } });
const tasks = argv._;

const binaryPath = process.env._;
const binaryName =
binaryPath && binaryPath.endsWith('/npx') ? 'npx mrm' : 'mrm';

// Custom config / tasks directory
if (argv.dir) {
const dir = path.resolve(argv.dir);
if (!isDirectory.sync(dir)) {
printError(`Directory “${dir}” not found.`);
process.exit(1);
}

directories.unshift(dir);
}

// Preset
const preset = argv.preset || 'default';
const isDefaultPreset = preset === 'default';
if (isDefaultPreset) {
directories.push(path.dirname(require.resolve('mrm-preset-default')));
} else {
const presetPackageName = getPackageName('preset', preset);
const presetPath = tryResolve(presetPackageName, preset);
if (!presetPath) {
printError(`Preset “${preset}” not found.
We’ve tried to load “${presetPackageName}” and “${preset}” globally installed npm packages.`);
process.exit(1);
}
directories = [path.dirname(presetPath)];
}

const options = getConfig(directories, 'config.json', argv);
if (tasks.length === 0 || tasks[0] === 'help') {
commandHelp();
} else {
run(tasks, directories, options, argv).catch(err => {
if (err.constructor === MrmUnknownAlias) {
printError(err.message);
} else if (err.constructor === MrmUnknownTask) {
const { taskName } = err.extra;
if (isDefaultPreset) {
const modules = directories
.slice(0, -1)
.map(d => `${d}/${taskName}/index.js`)
.concat([
`“${taskName}” in the default mrm tasks`,
`npm install -g mrm-task-${taskName}`,
`npm install -g ${taskName}`,
]);
printError(
`${err.message}
async function main() {
const argv = minimist(process.argv.slice(2), { alias: { i: 'interactive' } });
const tasks = argv._;

const binaryPath = process.env._;
const binaryName =
binaryPath && binaryPath.endsWith('/npx') ? 'npx mrm' : 'mrm';

// Preset
const preset = argv.preset || 'default';
const isDefaultPreset = preset === 'default';
const directories = await resolveDirectories(defaultDirectories);
const options = getConfig(directories, 'config.json', argv);
if (tasks.length === 0 || tasks[0] === 'help') {
commandHelp();
} else {
run(tasks, directories, options, argv).catch(err => {
if (err.constructor === MrmUnknownAlias) {
printError(err.message);
} else if (err.constructor === MrmUnknownTask) {
const { taskName } = err.extra;
if (isDefaultPreset) {
const modules = directories
.slice(0, -1)
.map(d => `${d}/${taskName}/index.js`)
.concat([
`“${taskName}” in the default mrm tasks`,
`npm install -g mrm-task-${taskName}`,
`npm install -g ${taskName}`,
]);
printError(
`${err.message}
We’ve tried these locations:
- ${modules.join('\n- ')}`
);
} else {
printError(`Task “${taskName}” not found in the “${preset}” preset.
);
} else {
printError(`Task “${taskName}” not found in the “${preset}” preset.
Note that when a preset is specified no default search locations are used.`);
}
} else if (err.constructor === MrmInvalidTask) {
printError(`${err.message}
}
} else if (err.constructor === MrmInvalidTask) {
printError(`${err.message}
Make sure your task module exports a function.`);
} else if (err.constructor === MrmUndefinedOption) {
const { unknown } = err.extra;
const values = unknown.map(name => [name, random()]);
const heading = `Required config options are missed: ${listify(
unknown
)}.`;
const cliHelp = ` ${binaryName} ${tasks.join(' ')} ${values
.map(([n, v]) => `--config:${n} "${v}"`)
.join(' ')}`;
if (isDefaultPreset) {
const userDirectories = directories.slice(0, -1);
printError(
`${heading}
} else if (err.constructor === MrmUndefinedOption) {
const { unknown } = err.extra;
const values = unknown.map(name => [name, random()]);
const heading = `Required config options are missed: ${listify(
unknown
)}.`;
const cliHelp = ` ${binaryName} ${tasks.join(' ')} ${values
.map(([n, v]) => `--config:${n} "${v}"`)
.join(' ')}`;
if (isDefaultPreset) {
const userDirectories = directories.slice(0, -1);
printError(
`${heading}
1. Create a “config.json” file:
Expand All @@ -152,69 +131,111 @@ In one of these folders:
${cliHelp}
`
);
} else {
printError(
`${heading}
);
} else {
printError(
`${heading}
You can pass the option via command line:
${cliHelp}
Note that when a preset is specified no default search locations are used.`
);
);
}
} else {
throw err;
}
});
}

async function resolveDirectories(paths) {
// Custom config / tasks directory
if (argv.dir) {
const dir = path.resolve(argv.dir);
if (!isDirectory.sync(dir)) {
printError(`Directory “${dir}” not found.`);
process.exit(1);
}
} else {
throw err;

paths.unshift(dir);
}
});
}

function commandHelp() {
console.log(
[
kleur.underline('Usage'),
getUsage(),
kleur.underline('Available tasks'),
getTasksList(options),
].join('\n\n')
);
}
if (isDefaultPreset) {
return [...paths, path.dirname(require.resolve('mrm-preset-default'))];
}

function getUsage() {
const commands = EXAMPLES.map(x => x[0] + x[1]);
const commandsWidth = longest(commands).length;
return EXAMPLES.map(([command, options, description]) =>
[
' ',
kleur.bold(binaryName),
kleur.cyan(command),
kleur.yellow(options),
padEnd('', commandsWidth - (command + options).length),
description && `# ${description}`,
].join(' ')
).join('\n');
}
const presetPackageName = getPackageName('preset', preset);
const presetPath = tryResolve(presetPackageName, preset);
if (presetPath) {
return [path.dirname(presetPath)];
}

const globalPackageToInstall = await getGlobalPackageName(
preset,
presetPackageName
);
if (!globalPackageToInstall) {
printError(`Preset “${preset}” not found.
We’ve tried to load “${presetPackageName}” and “${preset}” globally installed npm packages.`);
process.exit(1);
}

console.log(kleur.green(`Installing ${globalPackageToInstall}...`));
await installGlobalPackage(globalPackageToInstall);

return resolveDirectories(paths);
}

function commandHelp() {
console.log(
[
kleur.underline('Usage'),
getUsage(),
kleur.underline('Available tasks'),
getTasksList(options),
].join('\n\n')
);
}

function getTasksList() {
const allTasks = getAllTasks(directories, options);
const names = sortBy(Object.keys(allTasks));
const nameColWidth = longest(names).length;

return names
.map(name => {
const description = Array.isArray(allTasks[name])
? `Runs ${listify(allTasks[name])}`
: allTasks[name];
return (
' ' + kleur.cyan(padEnd(name, nameColWidth)) + ' ' + description
);
})
.join('\n');
function getUsage() {
const commands = EXAMPLES.map(x => x[0] + x[1]);
const commandsWidth = longest(commands).length;
return EXAMPLES.map(([command, opts, description]) =>
[
' ',
kleur.bold(binaryName),
kleur.cyan(command),
kleur.yellow(opts),
padEnd('', commandsWidth - (command + opts).length),
description && `# ${description}`,
].join(' ')
).join('\n');
}

function getTasksList() {
const allTasks = getAllTasks(directories, options);
const names = sortBy(Object.keys(allTasks));
const nameColWidth = longest(names).length;

return names
.map(name => {
const description = Array.isArray(allTasks[name])
? `Runs ${listify(allTasks[name])}`
: allTasks[name];
return (
' ' + kleur.cyan(padEnd(name, nameColWidth)) + ' ' + description
);
})
.join('\n');
}
}

function printError(message) {
console.log();
console.error(kleur.bold().red(message));
console.log();
}

main();
Loading

0 comments on commit 87dc649

Please sign in to comment.