Skip to content

Commit

Permalink
Close GH-797: Decoupled prompting from the inner architecture.. Fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
satazor committed Aug 19, 2013
1 parent 12baabc commit 9fa08fe
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 61 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ through the `bower.commands` object.
var bower = require('bower');

bower.commands
.install(paths, options)
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
Expand All @@ -236,15 +236,32 @@ bower.commands
});
```

Commands emit three types of events: `log`, `end`, and `error`.
Commands emit four types of events: `log`, `prompt`, `end`, `error`.

* `log` is emitted to report the state/progress of the command.
* `prompt` is emitted whenever the user needs to be prompted.
* `error` will only be emitted if something goes wrong.
* `end` is emitted when the command successfully ends.

For a better of idea how this works, you may want to check out [our bin
file](https://github.com/bower/bower/blob/master/bin/bower).

When using bower programmatically, prompting is disabled by default. Though you can enable it when calling commands with `interactive: true` in the config.
This requires you to listen for the `prompt` event and handle the prompting yourself. The easiest way is to use the [inquirer](https://npmjs.org/package/inquirer) npm module like so:

```js
var inquirer = require('inquirer');

bower.commands
.install(['jquery'], { save: true }, { interactive: true })
// ..

This comment has been minimized.

Copy link
@fella-72

fella-72 Jun 21, 2015

Updates installed packages to their newest version according to bower.jsonLeave a line note

.on('prompt', function (prompts, callback) {
inquirer.prompt(prompts, function (answers) {
callback(answers);
});
});
```


## Completion (experimental)

Expand Down
15 changes: 8 additions & 7 deletions bin/bower
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env node

process.title = 'bower';

var path = require('path');
var tty = require('tty');
var mout = require('mout');
var updateNotifier = require('update-notifier');
var Logger = require('bower-logger');
Expand All @@ -21,8 +22,6 @@ var emitter;
var notifier;
var levels = Logger.LEVELS;

process.title = 'bower';

options = cli.readOptions({
version: { type: Boolean, shorthand: 'v' },
help: { type: Boolean, shorthand: 'h' },
Expand All @@ -49,10 +48,6 @@ if (bower.config.silent) {
loglevel = levels[bower.config.loglevel] || levels.info;
}

// Enable interactive if terminal is TTY,
// loglevel is equal or lower then conflict
bower.config.interactive = tty.isatty(1) && loglevel <= levels.conflict;

// Get the command to execute
while (options.argv.remain.length) {
command = options.argv.remain.join(' ');
Expand Down Expand Up @@ -119,6 +114,12 @@ emitter
if (levels[log.level] >= loglevel) {
renderer.log(log);

This comment has been minimized.

Copy link
@fella-72

fella-72 Jun 21, 2015

Leave a line 📝 Updates installed packages to their newest version according to bower.json

}
})
.on('prompt', function (prompt, callback) {
renderer.prompt(prompt)
.then(function (answer) {
callback(answer);
});
});

// Check for newer version of Bower
Expand Down
49 changes: 34 additions & 15 deletions lib/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,44 @@ var mout = require('mout');
var fs = require('graceful-fs');
var path = require('path');
var Q = require('q');
var inquirer = require('inquirer');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var GitFsResolver = require('../core/resolvers/GitFsResolver');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
var createError = require('../util/createError');

function init(config) {
var project;
var logger = new Logger();

config = mout.object.deepFillIn(config || {}, defaultConfig);

// This command requires interactive to be enabled
if (!config.interactive) {
process.nextTick(function () {
logger.emit('error', createError('Register requires interactive', 'ENOINT'));
});
return logger;
}

project = new Project(config, logger);

// Start with existing JSON details
readJson(project, logger)
// Fill in defaults
.then(setDefaults.bind(null, config))
// Now prompt user to make changes
.then(promptUser)
.then(promptUser.bind(null, logger))
// Set ignore based on the response
.spread(setIgnore)
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project))
.spread(saveJson.bind(null, project, logger))
.then(function (json) {
logger.emit('end', json);
})
Expand All @@ -52,16 +61,29 @@ function readJson(project, logger) {
});
}

function saveJson(project, json) {
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function (value, key) {
if (value == null || mout.lang.isEmpty(value)) {
delete json[key];
}
});

// Save json (true forces file creation)
return project.saveJson(true);
logger.info('json', 'Generated json', { json: json });

// Confirm the json with the user
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Looks good?'
})
.then(function (good) {
if (!good) {
return null;
}

// Save json (true forces file creation)
return project.saveJson(true);
});
}

function setDefaults(config, json) {
Expand Down Expand Up @@ -151,9 +173,7 @@ function setDefaults(config, json) {
});
}

function promptUser(json) {
var deferred = Q.defer();

function promptUser(logger, json) {
var questions = [
{
'name': 'name',
Expand Down Expand Up @@ -223,7 +243,8 @@ function promptUser(json) {
}
];

inquirer.prompt(questions, function (answers) {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
json.name = answers.name;
json.version = answers.version;
json.description = answers.description;
Expand All @@ -234,10 +255,8 @@ function promptUser(json) {
json.homepage = answers.homepage;
json.private = answers.private || null;

return deferred.resolve([json, answers]);
return [json, answers];
});

return deferred.promise;
}

function toArray(value, splitter) {
Expand Down
9 changes: 6 additions & 3 deletions lib/commands/register.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var mout = require('mout');
var Q = require('q');
var promptly = require('promptly');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
Expand Down Expand Up @@ -55,8 +54,12 @@ function register(name, url, config) {
}

// Confirm if the user really wants to register
return Q.nfcall(promptly.confirm, 'Registering a package will make it visible and installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue? (y/n)');
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function (result) {
// If user response was negative, abort
Expand Down
6 changes: 6 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var tty = require('tty');
var mout = require('mout');
var config = require('bower-config').read();
var cli = require('./util/cli');
Expand All @@ -6,6 +7,11 @@ var cli = require('./util/cli');
// and conflicts with --json
delete config.json;

// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = process.title === 'bower' && tty.isatty(1);
}

// Merge common CLI options into the config
mout.object.mixIn(config, cli.readOptions({
force: { type: Boolean, shorthand: 'f' },
Expand Down
24 changes: 16 additions & 8 deletions lib/core/Manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var path = require('path');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var fs = require('graceful-fs');
var promptly = require('promptly');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('./PackageRepository');
var semver = require('../util/semver');
Expand Down Expand Up @@ -692,20 +691,29 @@ Manager.prototype._electSuitable = function (name, semvers, nonSemvers) {
});

choices = picks.map(function (pick, index) { return index + 1; });
return Q.nfcall(promptly.choose, 'Choice:', choices, {
validator: function (choice) {
if (choice.charAt(0) === '!') {
choice = choice.substr(1);
save = true;
return Q.nfcall(this._logger.prompt.bind(this._logger), {
type: 'input',
message: 'Answer:',
validate: function (choice) {
choice = Number(mout.string.trim(choice.trim(), '!'));

if (!choice || choice < 1 || choice > picks.length) {
return 'Invalid choice';
}

return choice;
return true;
}
})
.then(function (choice) {
var pick = picks[choice - 1];
var pick;
var resolution;

// Sanitize choice
choice = choice.trim();
save = /^!/.test(choice) || /!$/.test(choice); // Save if prefixed or suffixed with !
choice = Number(mout.string.trim(choice, '!'));
pick = picks[choice - 1];

// Store choice into resolutions
if (pick.target === '*') {
resolution = pick.pkgMeta._release || '*';
Expand Down
19 changes: 8 additions & 11 deletions lib/core/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var fs = require('graceful-fs');
var Q = require('q');
var mout = require('mout');
var rimraf = require('rimraf');
var promptly = require('promptly');
var endpointParser = require('bower-endpoint-parser');
var Logger = require('bower-logger');
var Manager = require('./Manager');
Expand Down Expand Up @@ -266,15 +265,10 @@ Project.prototype.uninstall = function (names, options) {
// Otherwise we need to figure it out if the user really wants to remove it,
// even with dependants
// As such we need to prompt the user with a meaningful message
dependantsNames = dependants
.map(function (dep) {
return dep.name;
})
.sort(function (name1, name2) {
return name1.localeCompare(name2);
});

dependantsNames = dependants.map(function (dep) { return dep.name; });
dependantsNames.sort(function (name1, name2) { return name1.localeCompare(name2); });
dependantsNames = mout.array.unique(dependantsNames);
dependants = dependants.map(function (dependant) { return that._manager.toData(dependant); });
message = dependantsNames.join(', ') + ' depends on ' + decEndpoint.name;
data = {
name: decEndpoint.name,
Expand All @@ -290,8 +284,11 @@ Project.prototype.uninstall = function (names, options) {

that._logger.conflict('mutual', message, data);

// Question the user
return Q.nfcall(promptly.confirm, 'Continue anyway? (y/n)')
// Prompt the user
return Q.nfcall(that._logger.prompt.bind(that._logger), {
type: 'confirm',
message: 'Continue anyway?'
})
.then(function (confirmed) {
// If the user decided to skip it, remove from the array so that it won't
// influence subsequent dependants
Expand Down
Loading

0 comments on commit 9fa08fe

Please sign in to comment.