Repository: ember-cli/ember-cli
Pull request #6510 (2016-12-02 09:46:25)

tasks/{npm,bower}: Improve install/uninstall messages
Example:

```
$ ember install ember-cli-mocha
NPM: Installed ember-cli-mocha
installing ember-cli-mocha
  install bower package ember-mocha-adapter
  cached https://github.com/teddyzeenny/ember-mocha-adapter.git#0.3.1
Bower: Installed ember-mocha-adapter=ember-mocha-adapter#~0.3.1
  install package ember-cli-chai
NPM: Installed ember-cli-chai@^0.2.0
  uninstall package ember-cli-qunit
NPM: Uninstalled ember-cli-qunit
Installed addon package.
```


Patch:
diff --git a/lib/tasks/bower-install.js b/lib/tasks/bower-install.js
index bf8e56712..8f50e5985 100644
--- a/lib/tasks/bower-install.js
+++ b/lib/tasks/bower-install.js
@@ -5,6 +5,7 @@ var path       = require('path');
 var existsSync = require('exists-sync');
 var Promise    = require('../ext/promise');
 var Task       = require('../models/task');
+var formatPackageList = require('../utilities/format-package-list');
 
 module.exports = Task.extend({
   init: function() {
@@ -27,7 +28,10 @@ module.exports = Task.extend({
       return;
     }
 
-    ui.startProgress(chalk.green('Installing browser packages via Bower'), chalk.green('.'));
+    var startMessage = this.formatStartMessage(packages);
+    var completeMessage = this.formatCompleteMessage(packages);
+
+    ui.startProgress(chalk.green(startMessage));
 
     var config = bowerConfig.read();
     config.interactive = true;
@@ -41,7 +45,7 @@ module.exports = Task.extend({
     })
       .finally(function() { ui.stopProgress(); })
       .then(function() {
-        ui.writeLine(chalk.green('Installed browser packages via Bower.'));
+        ui.writeLine(chalk.green(completeMessage));
       });
 
     function logBowerMessage(message) {
@@ -61,5 +65,13 @@ module.exports = Task.extend({
         ui.writeLine('  ' + chalk.green(message.id) + ' ' + message.message);
       }
     }
+  },
+
+  formatStartMessage: function(packages) {
+    return 'Bower: Installing ' + formatPackageList(packages) + ' ...';
+  },
+
+  formatCompleteMessage: function(packages) {
+    return 'Bower: Installed ' + formatPackageList(packages);
   }
 });
diff --git a/lib/tasks/npm-install.js b/lib/tasks/npm-install.js
index b02234e2b..c8b6cc072 100644
--- a/lib/tasks/npm-install.js
+++ b/lib/tasks/npm-install.js
@@ -4,11 +4,11 @@
 var path       = require('path');
 var existsSync = require('exists-sync');
 var NpmTask    = require('./npm-task');
+var formatPackageList = require('../utilities/format-package-list');
 
 module.exports = NpmTask.extend({
   command: 'install',
-  startProgressMessage: 'Installing packages for tooling via npm',
-  completionMessage: 'Installed packages for tooling via npm.',
+
   run: function() {
     var ui = this.ui;
     var packageJson = path.join(this.project.root, 'package.json');
@@ -19,5 +19,13 @@ module.exports = NpmTask.extend({
     } else {
       return this._super.run.apply(this, arguments);
     }
+  },
+
+  formatStartMessage: function(packages) {
+    return 'NPM: Installing ' + formatPackageList(packages) + ' ...';
+  },
+
+  formatCompleteMessage: function(packages) {
+    return 'NPM: Installed ' + formatPackageList(packages);
   }
 });
diff --git a/lib/tasks/npm-task.js b/lib/tasks/npm-task.js
index c48d162e0..9abd88ced 100644
--- a/lib/tasks/npm-task.js
+++ b/lib/tasks/npm-task.js
@@ -8,10 +8,6 @@ var Task  = require('../models/task');
 module.exports = Task.extend({
   // The command to run: can be 'install' or 'uninstall'
   command: '',
-  // Message to send to ui.startProgress
-  startProgressMessage: '',
-  // Message to send to ui.writeLine on completion
-  completionMessage: '',
 
   init: function() {
     this._super.apply(this, arguments);
@@ -19,7 +15,11 @@ module.exports = Task.extend({
   },
 
   run: function(options) {
-    this.ui.startProgress(chalk.green(this.startProgressMessage), chalk.green('.'));
+    var ui = this.ui;
+    var startMessage = this.formatStartMessage(options.packages);
+    var completeMessage = this.formatCompleteMessage(options.packages);
+
+    ui.startProgress(chalk.green(startMessage));
 
     var args = [this.command];
 
@@ -50,15 +50,19 @@ module.exports = Task.extend({
     }
 
     return this.npm(args)
-      .finally(this.finally.bind(this))
-      .then(this.announceCompletion.bind(this));
+      .finally(function() {
+        ui.stopProgress();
+      })
+      .then(function() {
+        ui.writeLine(chalk.green(completeMessage));
+      });
   },
 
-  announceCompletion: function() {
-    this.ui.writeLine(chalk.green(this.completionMessage));
+  formatStartMessage: function(/* packages */) {
+    return '';
   },
 
-  finally: function() {
-    this.ui.stopProgress();
+  formatCompleteMessage: function(/* packages */) {
+    return '';
   }
 });
diff --git a/lib/tasks/npm-uninstall.js b/lib/tasks/npm-uninstall.js
index 79db8f8a0..3783be4a3 100644
--- a/lib/tasks/npm-uninstall.js
+++ b/lib/tasks/npm-uninstall.js
@@ -3,9 +3,16 @@
 // Runs `npm uninstall` in cwd
 
 var NpmTask = require('./npm-task');
+var formatPackageList = require('../utilities/format-package-list');
 
 module.exports = NpmTask.extend({
   command: 'uninstall',
-  startProgressMessage: 'Uninstalling packages for tooling via npm',
-  completionMessage: 'Uninstalled packages for tooling via npm.'
+
+  formatStartMessage: function(packages) {
+    return 'NPM: Uninstalling ' + formatPackageList(packages) + ' ...';
+  },
+
+  formatCompleteMessage: function(packages) {
+    return 'NPM: Uninstalled ' + formatPackageList(packages);
+  }
 });
diff --git a/lib/utilities/format-package-list.js b/lib/utilities/format-package-list.js
new file mode 100644
index 000000000..d9e36f996
--- /dev/null
+++ b/lib/utilities/format-package-list.js
@@ -0,0 +1,17 @@
+'use strict';
+
+module.exports = function formatPackageList(packages) {
+  if (Array.isArray(packages)) {
+    if (packages.length === 1) {
+      return packages[0];
+    } else if (packages.length === 2) {
+      return packages[0] + ' and ' + packages[1];
+    } else if (packages.length === 3) {
+      return packages[0] + ', ' + packages[1] + ' and ' + packages[2];
+    } else if (packages.length > 3) {
+      return packages[0] + ', ' + packages[1] + ' and ' + (packages.length - 2) + ' other packages';
+    }
+  }
+
+  return 'dependencies';
+};


Test patch:
diff --git a/tests/unit/utilities/format-package-list-test.js b/tests/unit/utilities/format-package-list-test.js
new file mode 100644
index 000000000..46c7c3275
--- /dev/null
+++ b/tests/unit/utilities/format-package-list-test.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var expect = require('chai').expect;
+var formatPackageList = require('../../../lib/utilities/format-package-list');
+
+describe('format-package-list', function() {
+  it('correctly formats package list with 1 item', function() {
+    var result = formatPackageList(['ember-foo']);
+    expect(result).to.equal('ember-foo');
+  });
+
+  it('correctly formats package list with 2 items', function() {
+    var result = formatPackageList(['ember-foo', 'bar']);
+    expect(result).to.equal('ember-foo and bar');
+  });
+
+  it('correctly formats package list with 3 items', function() {
+    var result = formatPackageList(['ember-foo', 'bar', 'baaaaz']);
+    expect(result).to.equal('ember-foo, bar and baaaaz');
+  });
+
+  it('correctly formats package list with 4 items', function() {
+    var result = formatPackageList(['ember-foo', 'bar', 'baaaaz', 'ember-blabla']);
+    expect(result).to.equal('ember-foo, bar and 2 other packages');
+  });
+
+  it('correctly formats package list with 100 items', function() {
+    var list = [];
+    for (var i = 1; i <= 100; i++) {
+      list.push('package-' + i);
+    }
+
+    var result = formatPackageList(list);
+    expect(result).to.equal('package-1, package-2 and 98 other packages');
+  });
+
+  it('returns generic "dependencies" without input', function() {
+    var result = formatPackageList();
+    expect(result).to.equal('dependencies');
+  });
+
+  it('returns generic "dependencies" for invalid input', function() {
+    var result = formatPackageList('foo');
+    expect(result).to.equal('dependencies');
+  });
+
+  it('returns generic "dependencies" for empty input', function() {
+    var result = formatPackageList([]);
+    expect(result).to.equal('dependencies');
+  });
+});