yeoman-generator 2.0.5 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,33 +13,23 @@ const help = module.exports;
13
13
  /**
14
14
  * Tries to get the description from a USAGE file one folder above the
15
15
  * source root otherwise uses a default description
16
+ *
17
+ * @return {String} Help message of the generator
16
18
  */
17
- help.help = function () {
19
+ help.help = function() {
18
20
  const filepath = path.resolve(this.sourceRoot(), '../USAGE');
19
21
  const exists = fs.existsSync(filepath);
20
22
 
21
- let out = [
22
- 'Usage:',
23
- ' ' + this.usage(),
24
- ''
25
- ];
23
+ let out = ['Usage:', ' ' + this.usage(), ''];
26
24
 
27
25
  // Build options
28
26
  if (Object.keys(this._options).length > 0) {
29
- out = out.concat([
30
- 'Options:',
31
- this.optionsHelp(),
32
- ''
33
- ]);
27
+ out = out.concat(['Options:', this.optionsHelp(), '']);
34
28
  }
35
29
 
36
30
  // Build arguments
37
31
  if (this._arguments.length > 0) {
38
- out = out.concat([
39
- 'Arguments:',
40
- this.argumentsHelp(),
41
- ''
42
- ]);
32
+ out = out.concat(['Arguments:', this.argumentsHelp(), '']);
43
33
  }
44
34
 
45
35
  // Append USAGE file is any
@@ -63,8 +53,10 @@ function formatArg(config) {
63
53
  /**
64
54
  * Output usage information for this given generator, depending on its arguments
65
55
  * or options
56
+ *
57
+ * @return {String} Usage information of the generator
66
58
  */
67
- help.usage = function () {
59
+ help.usage = function() {
68
60
  const options = Object.keys(this._options).length ? '[options]' : '';
69
61
  let name = this.options.namespace;
70
62
  let args = '';
@@ -89,7 +81,7 @@ help.usage = function () {
89
81
  * @param {String} description
90
82
  */
91
83
 
92
- help.desc = function (description) {
84
+ help.desc = function(description) {
93
85
  this.description = description || '';
94
86
  return this;
95
87
  };
@@ -98,7 +90,7 @@ help.desc = function (description) {
98
90
  * Get help text for arguments
99
91
  * @returns {String} Text of options in formatted table
100
92
  */
101
- help.argumentsHelp = function () {
93
+ help.argumentsHelp = function() {
102
94
  const rows = this._arguments.map(config => {
103
95
  return [
104
96
  '',
@@ -116,7 +108,7 @@ help.argumentsHelp = function () {
116
108
  * Get help text for options
117
109
  * @returns {String} Text of options in formatted table
118
110
  */
119
- help.optionsHelp = function () {
111
+ help.optionsHelp = function() {
120
112
  const options = _.reject(this._options, x => x.hide);
121
113
 
122
114
  const rows = options.map(opt => {
@@ -125,7 +117,7 @@ help.optionsHelp = function () {
125
117
  opt.alias ? `-${opt.alias}, ` : '',
126
118
  `--${opt.name}`,
127
119
  opt.description ? `# ${opt.description}` : '',
128
- (opt.default !== undefined && opt.default !== '') ? 'Default: ' + opt.default : ''
120
+ opt.default !== undefined && opt.default !== '' ? 'Default: ' + opt.default : ''
129
121
  ];
130
122
  });
131
123
 
@@ -2,7 +2,6 @@
2
2
  const assert = require('assert');
3
3
  const _ = require('lodash');
4
4
  const dargs = require('dargs');
5
- const async = require('async');
6
5
  const chalk = require('chalk');
7
6
 
8
7
  /**
@@ -15,63 +14,82 @@ const install = module.exports;
15
14
  * Combine package manager cmd line arguments and run the `install` command.
16
15
  *
17
16
  * During the `install` step, every command will be scheduled to run once, on the
18
- * run loop. This means you can use `Promise.then` to log information, but don't
19
- * return it or mix it with `this.async` as it'll dead lock the process.
17
+ * run loop.
20
18
  *
21
19
  * @param {String} installer Which package manager to use
22
20
  * @param {String|Array} [paths] Packages to install. Use an empty string for `npm install`
23
21
  * @param {Object} [options] Options to pass to `dargs` as arguments
24
22
  * @param {Object} [spawnOptions] Options to pass `child_process.spawn`. ref
25
23
  * https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
26
- * @return {Promise} Resolved on installation success, rejected otherwise
27
24
  */
28
25
 
29
- install.runInstall = function (installer, paths, options, spawnOptions) {
30
- return new Promise((resolve, reject) => {
31
- options = options || {};
32
- spawnOptions = spawnOptions || {};
33
- paths = Array.isArray(paths) ? paths : (paths && paths.split(' ')) || [];
26
+ install.scheduleInstallTask = function(installer, paths, options, spawnOptions) {
27
+ options = options || {};
28
+ spawnOptions = spawnOptions || {};
29
+ paths = Array.isArray(paths) ? paths : (paths && paths.split(' ')) || [];
34
30
 
35
- let args = ['install'].concat(paths).concat(dargs(options));
31
+ let args = ['install'].concat(paths).concat(dargs(options));
36
32
 
37
- // Yarn uses the `add` command to specifically add a package to a project
38
- if (installer === 'yarn' && paths.length > 0) {
39
- args[0] = 'add';
40
- }
33
+ // Yarn uses the `add` command to specifically add a package to a project
34
+ if (installer === 'yarn' && paths.length > 0) {
35
+ args[0] = 'add';
36
+ }
41
37
 
42
- // Only for npm, use a minimum cache of one day
43
- if (installer === 'npm') {
44
- args = args.concat(['--cache-min', 24 * 60 * 60]);
45
- }
38
+ // Only for npm, use a minimum cache of one day
39
+ if (installer === 'npm') {
40
+ args = args.concat(['--cache-min', 24 * 60 * 60]);
41
+ }
46
42
 
47
- // Return early if we're skipping installation
48
- if (this.options.skipInstall) {
49
- this.log('Skipping install command: ' + chalk.yellow(installer + ' ' + args.join(' ')));
50
- return resolve();
51
- }
43
+ // Return early if we're skipping installation
44
+ if (this.options.skipInstall || this.options['skip-install']) {
45
+ this.log(
46
+ 'Skipping install command: ' + chalk.yellow(installer + ' ' + args.join(' '))
47
+ );
48
+ return;
49
+ }
52
50
 
53
- this.env.runLoop.add('install', done => {
51
+ this.env.runLoop.add(
52
+ 'install',
53
+ done => {
54
54
  this.emit(`${installer}Install`, paths);
55
55
  this.spawnCommand(installer, args, spawnOptions)
56
- .on('error', err => {
57
- this.log(chalk.red('Could not finish installation. \n') +
58
- 'Please install ' + installer + ' with ' +
59
- chalk.yellow('npm install -g ' + installer) + ' and try again. \n' +
60
- 'If ' + installer + ' is already installed, try running the following command manually: ' + chalk.yellow(installer + ' ' + args.join(' '))
56
+ .on('error', error => {
57
+ this.log(
58
+ chalk.red('Could not finish installation. \n') +
59
+ 'Please install ' +
60
+ installer +
61
+ ' with ' +
62
+ chalk.yellow('npm install -g ' + installer) +
63
+ ' and try again. \n' +
64
+ 'If ' +
65
+ installer +
66
+ ' is already installed, try running the following command manually: ' +
67
+ chalk.yellow(installer + ' ' + args.join(' '))
61
68
  );
62
- reject(err);
69
+ if (this.options.forceInstall || this.options['force-install']) {
70
+ this.emit('error', error);
71
+ }
63
72
  done();
64
73
  })
65
- .on('exit', () => {
74
+ .on('exit', (code, signal) => {
66
75
  this.emit(`${installer}Install:end`, paths);
67
- resolve();
76
+ if (
77
+ (code || signal) &&
78
+ (this.options.forceInstall || this.options['force-install'])
79
+ ) {
80
+ this.emit(
81
+ 'error',
82
+ new Error(`Installation of ${installer} failed with code ${code || signal}`)
83
+ );
84
+ }
68
85
  done();
69
86
  });
70
- }, {
87
+ },
88
+ {
71
89
  once: installer + ' ' + args.join(' '),
72
90
  run: false
73
- });
74
- });
91
+ }
92
+ );
75
93
  };
76
94
 
77
95
  /**
@@ -82,30 +100,30 @@ install.runInstall = function (installer, paths, options, spawnOptions) {
82
100
  * this.installDependencies({
83
101
  * bower: true,
84
102
  * npm: true
85
- * }).then(() => console.log('Everything is ready!'));
103
+ * });
86
104
  *
87
105
  * @example
88
106
  * this.installDependencies({
89
107
  * yarn: {force: true},
90
108
  * npm: false
91
- * }).then(() => console.log('Everything is ready!'));
109
+ * });
92
110
  *
93
111
  * @param {Object} [options]
94
112
  * @param {Boolean|Object} [options.npm=true] - whether to run `npm install` or can be options to pass to `dargs` as arguments
95
113
  * @param {Boolean|Object} [options.bower=true] - whether to run `bower install` or can be options to pass to `dargs` as arguments
96
114
  * @param {Boolean|Object} [options.yarn=false] - whether to run `yarn install` or can be options to pass to `dargs` as arguments
97
115
  * @param {Boolean} [options.skipMessage=false] - whether to log the used commands
98
- * @return {Promise} Resolved once done, rejected if errors
99
116
  */
100
- install.installDependencies = function (options) {
117
+ install.installDependencies = function(options) {
101
118
  options = options || {};
102
- const commands = [];
103
119
  const msg = {
104
120
  commands: [],
105
- template: _.template('\n\nI\'m all done. ' +
106
- '<%= skipInstall ? "Just run" : "Running" %> <%= commands %> ' +
107
- '<%= skipInstall ? "" : "for you " %>to install the required dependencies.' +
108
- '<% if (!skipInstall) { %> If this fails, try running the command yourself.<% } %>\n\n')
121
+ template: _.template(
122
+ "\n\nI'm all done. " +
123
+ '<%= skipInstall ? "Just run" : "Running" %> <%= commands %> ' +
124
+ '<%= skipInstall ? "" : "for you " %>to install the required dependencies.' +
125
+ '<% if (!skipInstall) { %> If this fails, try running the command yourself.<% } %>\n\n'
126
+ )
109
127
  };
110
128
 
111
129
  const getOptions = options => {
@@ -114,53 +132,36 @@ install.installDependencies = function (options) {
114
132
 
115
133
  if (options.npm !== false) {
116
134
  msg.commands.push('npm install');
117
- commands.push(cb => {
118
- this.npmInstall(null, getOptions(options.npm)).then(
119
- val => cb(null, val),
120
- cb
121
- );
122
- });
135
+ this.npmInstall(null, getOptions(options.npm));
123
136
  }
124
137
 
125
138
  if (options.yarn) {
126
139
  msg.commands.push('yarn install');
127
- commands.push(cb => {
128
- this.yarnInstall(null, getOptions(options.yarn)).then(
129
- val => cb(null, val),
130
- cb
131
- );
132
- });
140
+ this.yarnInstall(null, getOptions(options.yarn));
133
141
  }
134
142
 
135
143
  if (options.bower !== false) {
136
144
  msg.commands.push('bower install');
137
- commands.push(cb => {
138
- this.bowerInstall(null, getOptions(options.bower)).then(
139
- val => cb(null, val),
140
- cb
141
- );
142
- });
145
+ this.bowerInstall(null, getOptions(options.bower));
143
146
  }
144
147
 
145
- assert(msg.commands.length, 'installDependencies needs at least one of `npm`, `bower` or `yarn` to run.');
148
+ assert(
149
+ msg.commands.length,
150
+ 'installDependencies needs at least one of `npm`, `bower` or `yarn` to run.'
151
+ );
146
152
 
147
153
  if (!options.skipMessage) {
148
- const tplValues = _.extend({
149
- skipInstall: false
150
- }, this.options, {
151
- commands: chalk.yellow.bold(msg.commands.join(' && '))
152
- });
154
+ const tplValues = _.extend(
155
+ {
156
+ skipInstall: false
157
+ },
158
+ this.options,
159
+ {
160
+ commands: chalk.yellow.bold(msg.commands.join(' && '))
161
+ }
162
+ );
153
163
  this.log(msg.template(tplValues));
154
164
  }
155
-
156
- return new Promise((resolve, reject) => {
157
- async.parallel(commands, (err, results) => {
158
- if (err) {
159
- return reject(err);
160
- }
161
- resolve(results);
162
- });
163
- });
164
165
  };
165
166
 
166
167
  /**
@@ -171,10 +172,9 @@ install.installDependencies = function (options) {
171
172
  * @param {String|Array} [cmpnt] Components to install
172
173
  * @param {Object} [options] Options to pass to `dargs` as arguments
173
174
  * @param {Object} [spawnOptions] Options to pass `child_process.spawn`.
174
- * @return {Promise} Resolved if install successful, rejected otherwise
175
175
  */
176
- install.bowerInstall = function (cmpnt, options, spawnOptions) {
177
- return this.runInstall('bower', cmpnt, options, spawnOptions);
176
+ install.bowerInstall = function(cmpnt, options, spawnOptions) {
177
+ this.scheduleInstallTask('bower', cmpnt, options, spawnOptions);
178
178
  };
179
179
 
180
180
  /**
@@ -185,10 +185,9 @@ install.bowerInstall = function (cmpnt, options, spawnOptions) {
185
185
  * @param {String|Array} [pkgs] Packages to install
186
186
  * @param {Object} [options] Options to pass to `dargs` as arguments
187
187
  * @param {Object} [spawnOptions] Options to pass `child_process.spawn`.
188
- * @return {Promise} Resolved if install successful, rejected otherwise
189
188
  */
190
- install.npmInstall = function (pkgs, options, spawnOptions) {
191
- return this.runInstall('npm', pkgs, options, spawnOptions);
189
+ install.npmInstall = function(pkgs, options, spawnOptions) {
190
+ this.scheduleInstallTask('npm', pkgs, options, spawnOptions);
192
191
  };
193
192
 
194
193
  /**
@@ -199,8 +198,7 @@ install.npmInstall = function (pkgs, options, spawnOptions) {
199
198
  * @param {String|Array} [pkgs] Packages to install
200
199
  * @param {Object} [options] Options to pass to `dargs` as arguments
201
200
  * @param {Object} [spawnOptions] Options to pass `child_process.spawn`.
202
- * @return {Promise} Resolved if install successful, rejected otherwise
203
201
  */
204
- install.yarnInstall = function (pkgs, options, spawnOptions) {
205
- return this.runInstall('yarn', pkgs, options, spawnOptions);
202
+ install.yarnInstall = function(pkgs, options, spawnOptions) {
203
+ this.scheduleInstallTask('yarn', pkgs, options, spawnOptions);
206
204
  };
@@ -11,23 +11,25 @@ const spawnCommand = module.exports;
11
11
  /**
12
12
  * Normalize a command across OS and spawn it (asynchronously).
13
13
  *
14
- * @param {String} command
15
- * @param {Array} args
16
- * @param {object} [opt]
14
+ * @param {String} command program to execute
15
+ * @param {Array} args list of arguments to pass to the program
16
+ * @param {object} [opt] any cross-spawn options
17
+ * @return {String} spawned process reference
17
18
  */
18
19
  spawnCommand.spawnCommand = (command, args, opt) => {
19
20
  opt = opt || {};
20
- return spawn(command, args, _.defaults(opt, {stdio: 'inherit'}));
21
+ return spawn(command, args, _.defaults(opt, { stdio: 'inherit' }));
21
22
  };
22
23
 
23
24
  /**
24
25
  * Normalize a command across OS and spawn it (synchronously).
25
26
  *
26
- * @param {String} command
27
- * @param {Array} args
28
- * @param {object} [opt]
27
+ * @param {String} command program to execute
28
+ * @param {Array} args list of arguments to pass to the program
29
+ * @param {object} [opt] any cross-spawn options
30
+ * @return {String} spawn.sync result
29
31
  */
30
32
  spawnCommand.spawnCommandSync = (command, args, opt) => {
31
33
  opt = opt || {};
32
- return spawn.sync(command, args, _.defaults(opt, {stdio: 'inherit'}));
34
+ return spawn.sync(command, args, _.defaults(opt, { stdio: 'inherit' }));
33
35
  };
@@ -27,7 +27,7 @@ user.git.name = () => {
27
27
  }
28
28
 
29
29
  if (shell.which('git')) {
30
- name = shell.exec('git config --get user.name', {silent: true}).stdout.trim();
30
+ name = shell.exec('git config --get user.name', { silent: true }).stdout.trim();
31
31
  nameCache.set(process.cwd(), name);
32
32
  }
33
33
 
@@ -47,7 +47,7 @@ user.git.email = () => {
47
47
  }
48
48
 
49
49
  if (shell.which('git')) {
50
- email = shell.exec('git config --get user.email', {silent: true}).stdout.trim();
50
+ email = shell.exec('git config --get user.email', { silent: true }).stdout.trim();
51
51
  emailCache.set(process.cwd(), email);
52
52
  }
53
53
 
package/lib/index.js CHANGED
@@ -75,7 +75,7 @@ class Generator extends EventEmitter {
75
75
  this.option('help', {
76
76
  type: Boolean,
77
77
  alias: 'h',
78
- description: 'Print the generator\'s options and usage'
78
+ description: "Print the generator's options and usage"
79
79
  });
80
80
 
81
81
  this.option('skip-cache', {
@@ -90,9 +90,21 @@ class Generator extends EventEmitter {
90
90
  default: false
91
91
  });
92
92
 
93
+ this.option('force-install', {
94
+ type: Boolean,
95
+ description: 'Fail on install dependencies error',
96
+ default: false
97
+ });
98
+
93
99
  // Checks required parameters
94
- assert(this.options.env, 'You must provide the environment object. Use env#create() to create a new generator.');
95
- assert(this.options.resolved, 'You must provide the resolved path value. Use env#create() to create a new generator.');
100
+ assert(
101
+ this.options.env,
102
+ 'You must provide the environment object. Use env#create() to create a new generator.'
103
+ );
104
+ assert(
105
+ this.options.resolved,
106
+ 'You must provide the resolved path value. Use env#create() to create a new generator.'
107
+ );
96
108
  this.env = this.options.env;
97
109
  this.resolved = this.options.resolved;
98
110
 
@@ -122,11 +134,13 @@ class Generator extends EventEmitter {
122
134
  rootPath = rootPath ? path.dirname(rootPath) : this.env.cwd;
123
135
 
124
136
  if (rootPath !== this.env.cwd) {
125
- this.log([
126
- '',
127
- 'Just found a `.yo-rc.json` in a parent directory.',
128
- 'Setting the project root at: ' + rootPath
129
- ].join('\n'));
137
+ this.log(
138
+ [
139
+ '',
140
+ 'Just found a `.yo-rc.json` in a parent directory.',
141
+ 'Setting the project root at: ' + rootPath
142
+ ].join('\n')
143
+ );
130
144
  this.destinationRoot(rootPath);
131
145
  }
132
146
 
@@ -153,7 +167,7 @@ class Generator extends EventEmitter {
153
167
  questions = promptSuggestion.prefillQuestions(this.config, questions);
154
168
 
155
169
  return this.env.adapter.prompt(questions).then(answers => {
156
- if (!this.options['skip-cache']) {
170
+ if (!this.options['skip-cache'] && !this.options.skipCache) {
157
171
  promptSuggestion.storeAnswers(this._globalConfig, questions, answers, false);
158
172
  promptSuggestion.storeAnswers(this.config, questions, answers, true);
159
173
  }
@@ -198,12 +212,17 @@ class Generator extends EventEmitter {
198
212
  const boolOptionRegex = /^no-/;
199
213
  if (config.type === Boolean && name.match(boolOptionRegex)) {
200
214
  const simpleName = name.replace(boolOptionRegex, '');
201
- return this.emit('error', new Error([
202
- `Option name ${chalk.yellow(name)} cannot start with ${chalk.red('no-')}\n`,
203
- `Option name prefixed by ${chalk.yellow('--no')} are parsed as implicit`,
204
- ` boolean. To use ${chalk.yellow('--' + name)} as an option, use\n`,
205
- chalk.cyan(` this.option('${simpleName}', {type: Boolean})`)
206
- ].join('')));
215
+ return this.emit(
216
+ 'error',
217
+ new Error(
218
+ [
219
+ `Option name ${chalk.yellow(name)} cannot start with ${chalk.red('no-')}\n`,
220
+ `Option name prefixed by ${chalk.yellow('--no')} are parsed as implicit`,
221
+ ` boolean. To use ${chalk.yellow('--' + name)} as an option, use\n`,
222
+ chalk.cyan(` this.option('${simpleName}', {type: Boolean})`)
223
+ ].join('')
224
+ )
225
+ );
207
226
  }
208
227
 
209
228
  if (this._options[name] === null || this._options[name] === undefined) {
@@ -348,7 +367,10 @@ class Generator extends EventEmitter {
348
367
  // If the help option was not provided, check whether the argument was
349
368
  // required, and whether a value was provided.
350
369
  if (config.required && position >= this.args.length) {
351
- return this.emit('error', new Error(`Did not provide required argument ${chalk.bold(config.name)}!`));
370
+ return this.emit(
371
+ 'error',
372
+ new Error(`Did not provide required argument ${chalk.bold(config.name)}!`)
373
+ );
352
374
  }
353
375
  });
354
376
  }
@@ -364,154 +386,205 @@ class Generator extends EventEmitter {
364
386
  * provided, the same values used to initialize the invoker are used to
365
387
  * initialize the invoked.
366
388
  *
367
- * @param {Function} [cb]
389
+ * @param {Function} [cb] Deprecated: prefer to use the promise interface
390
+ * @return {Promise} Resolved once the process finish
368
391
  */
369
392
  run(cb) {
370
- cb = cb || (() => {});
371
-
372
- const self = this;
373
- this._running = true;
374
- this.emit('run');
375
-
376
- const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
377
- const validMethods = methods.filter(methodIsValid);
378
- assert(validMethods.length, 'This Generator is empty. Add at least one method for it to run.');
379
-
380
- this.env.runLoop.once('end', () => {
381
- this.emit('end');
382
- cb();
383
- });
384
-
385
- // Ensure a prototype method is a candidate run by default
386
- function methodIsValid(name) {
387
- return name.charAt(0) !== '_' && name !== 'constructor';
388
- }
389
-
390
- function addMethod(method, methodName, queueName) {
391
- queueName = queueName || 'default';
392
- debug(`Queueing ${methodName} in ${queueName}`);
393
- self.env.runLoop.add(queueName, completed => {
394
- debug(`Running ${methodName}`);
395
- self.emit(`method:${methodName}`);
396
-
397
- runAsync(function () {
398
- self.async = () => this.async();
399
- return method.apply(self, self.args);
400
- })().then(completed).catch(err => {
401
- debug(`An error occured while running ${methodName}`, err);
402
-
403
- // Ensure we emit the error event outside the promise context so it won't be
404
- // swallowed when there's no listeners.
405
- setImmediate(() => {
406
- self.emit('error', err);
407
- cb(err);
408
- });
409
- });
393
+ const promise = new Promise((resolve, reject) => {
394
+ const self = this;
395
+ this._running = true;
396
+ this.emit('run');
397
+
398
+ const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
399
+ const validMethods = methods.filter(methodIsValid);
400
+ assert(
401
+ validMethods.length,
402
+ 'This Generator is empty. Add at least one method for it to run.'
403
+ );
404
+
405
+ this.env.runLoop.once('end', () => {
406
+ this.emit('end');
407
+ resolve();
410
408
  });
411
- }
412
409
 
413
- function addInQueue(name) {
414
- const item = Object.getPrototypeOf(self)[name];
415
- const queueName = self.env.runLoop.queueNames.indexOf(name) === -1 ? null : name;
416
-
417
- // Name points to a function; run it!
418
- if (typeof item === 'function') {
419
- return addMethod(item, name, queueName);
410
+ // Ensure a prototype method is a candidate run by default
411
+ function methodIsValid(name) {
412
+ return name.charAt(0) !== '_' && name !== 'constructor';
420
413
  }
421
414
 
422
- // Not a queue hash; stop
423
- if (!queueName) {
424
- return;
415
+ function addMethod(method, methodName, queueName) {
416
+ queueName = queueName || 'default';
417
+ debug(`Queueing ${methodName} in ${queueName}`);
418
+ self.env.runLoop.add(queueName, completed => {
419
+ debug(`Running ${methodName}`);
420
+ self.emit(`method:${methodName}`);
421
+
422
+ runAsync(function() {
423
+ self.async = () => this.async();
424
+ return method.apply(self, self.args);
425
+ })()
426
+ .then(completed)
427
+ .catch(err => {
428
+ debug(`An error occured while running ${methodName}`, err);
429
+
430
+ // Ensure we emit the error event outside the promise context so it won't be
431
+ // swallowed when there's no listeners.
432
+ setImmediate(() => {
433
+ self.emit('error', err);
434
+ reject(err);
435
+ });
436
+ });
437
+ });
425
438
  }
426
439
 
427
- // Run each queue items
428
- _.each(item, (method, methodName) => {
429
- if (!_.isFunction(method) || !methodIsValid(methodName)) {
440
+ function addInQueue(name) {
441
+ const item = Object.getPrototypeOf(self)[name];
442
+ const queueName = self.env.runLoop.queueNames.indexOf(name) === -1 ? null : name;
443
+
444
+ // Name points to a function; run it!
445
+ if (typeof item === 'function') {
446
+ return addMethod(item, name, queueName);
447
+ }
448
+
449
+ // Not a queue hash; stop
450
+ if (!queueName) {
430
451
  return;
431
452
  }
432
453
 
433
- addMethod(method, methodName, queueName);
434
- });
435
- }
454
+ // Run each queue items
455
+ _.each(item, (method, methodName) => {
456
+ if (!_.isFunction(method) || !methodIsValid(methodName)) {
457
+ return;
458
+ }
436
459
 
437
- validMethods.forEach(addInQueue);
460
+ addMethod(method, methodName, queueName);
461
+ });
462
+ }
438
463
 
439
- const writeFiles = () => {
440
- this.env.runLoop.add('conflicts', this._writeFiles.bind(this), {
441
- once: 'write memory fs to disk'
442
- });
443
- };
464
+ validMethods.forEach(addInQueue);
444
465
 
445
- this.env.sharedFs.on('change', writeFiles);
446
- writeFiles();
466
+ const writeFiles = () => {
467
+ this.env.runLoop.add('conflicts', this._writeFiles.bind(this), {
468
+ once: 'write memory fs to disk'
469
+ });
470
+ };
447
471
 
448
- // Add the default conflicts handling
449
- this.env.runLoop.add('conflicts', done => {
450
- this.conflicter.resolve(err => {
451
- if (err) {
452
- this.emit('error', err);
453
- }
472
+ this.env.sharedFs.on('change', writeFiles);
473
+ writeFiles();
454
474
 
455
- done();
475
+ // Add the default conflicts handling
476
+ this.env.runLoop.add('conflicts', done => {
477
+ this.conflicter.resolve(err => {
478
+ if (err) {
479
+ this.emit('error', err);
480
+ }
481
+
482
+ done();
483
+ });
456
484
  });
485
+
486
+ _.invokeMap(this._composedWith, 'run');
457
487
  });
458
488
 
459
- _.invokeMap(this._composedWith, 'run');
460
- return this;
489
+ // Maintain backward compatibility with the callback function
490
+ if (_.isFunction(cb)) {
491
+ promise.then(cb, cb);
492
+ }
493
+
494
+ return promise;
461
495
  }
462
496
 
463
497
  /**
464
498
  * Compose this generator with another one.
465
- * @param {String} namespace The generator namespace to compose with
499
+ * @param {String|Object} generator The path to the generator module or an object (see examples)
466
500
  * @param {Object} options The options passed to the Generator
467
- * @param {Object} [settings] Settings hash on the composition relation
468
- * @param {string} [settings.local] Path to a locally stored generator
469
- * @param {String} [settings.link="weak"] If "strong", the composition will occured
470
- * even when the composition is initialized by
471
- * the end user
472
- * @return {this}
501
+ * @return {this} This generator
473
502
  *
474
503
  * @example <caption>Using a peerDependency generator</caption>
475
504
  * this.composeWith('bootstrap', { sass: true });
476
505
  *
477
506
  * @example <caption>Using a direct dependency generator</caption>
478
507
  * this.composeWith(require.resolve('generator-bootstrap/app/main.js'), { sass: true });
508
+ *
509
+ * @example <caption>Passing a Generator class</caption>
510
+ * this.composeWith({ Generator: MyGenerator, path: '../generator-bootstrap/app/main.js' }, { sass: true });
479
511
  */
480
- composeWith(modulePath, options) {
481
- let generator;
482
- options = options || {};
512
+ composeWith(generator, options) {
513
+ let instantiatedGenerator;
483
514
 
484
- // Pass down the default options so they're correctly mirrored down the chain.
485
- options = _.extend({
486
- skipInstall: this.options.skipInstall,
487
- 'skip-install': this.options.skipInstall,
488
- skipCache: this.options.skipCache,
489
- 'skip-cache': this.options.skipCache
490
- }, options);
491
-
492
- try {
493
- const Generator = require(modulePath); // eslint-disable-line import/no-dynamic-require
494
- Generator.resolved = require.resolve(modulePath);
495
- Generator.namespace = this.env.namespace(modulePath);
496
- generator = this.env.instantiate(Generator, {
515
+ const instantiate = (Generator, path) => {
516
+ Generator.resolved = require.resolve(path);
517
+ Generator.namespace = this.env.namespace(path);
518
+
519
+ return this.env.instantiate(Generator, {
497
520
  options,
498
521
  arguments: options.arguments
499
522
  });
500
- } catch (err) {
501
- if (err.code === 'MODULE_NOT_FOUND') {
502
- generator = this.env.create(modulePath, {
503
- options,
504
- arguments: options.arguments
505
- });
506
- } else {
507
- throw err;
523
+ };
524
+
525
+ options = options || {};
526
+
527
+ // Pass down the default options so they're correctly mirrored down the chain.
528
+ options = _.extend(
529
+ {
530
+ skipInstall: this.options.skipInstall || this.options['skip-install'],
531
+ 'skip-install': this.options.skipInstall || this.options['skip-install'],
532
+ skipCache: this.options.skipCache || this.options['skip-cache'],
533
+ 'skip-cache': this.options.skipCache || this.options['skip-cache'],
534
+ forceInstall: this.options.forceInstall || this.options['force-install'],
535
+ 'force-install': this.options.forceInstall || this.options['force-install']
536
+ },
537
+ options
538
+ );
539
+
540
+ if (typeof generator === 'string') {
541
+ try {
542
+ const Generator = require(generator); // eslint-disable-line import/no-dynamic-require
543
+ instantiatedGenerator = instantiate(Generator, generator);
544
+ } catch (err) {
545
+ if (err.code === 'MODULE_NOT_FOUND') {
546
+ instantiatedGenerator = this.env.create(generator, {
547
+ options,
548
+ arguments: options.arguments
549
+ });
550
+ } else {
551
+ throw err;
552
+ }
508
553
  }
554
+ } else {
555
+ assert(
556
+ generator.Generator,
557
+ `${chalk.red('Missing Generator property')}\n` +
558
+ `When passing an object to Generator${chalk.cyan(
559
+ '#composeWith'
560
+ )} include the generator class to run in the ${chalk.cyan(
561
+ 'Generator'
562
+ )} property\n\n` +
563
+ `this.composeWith({\n` +
564
+ ` ${chalk.yellow('Generator')}: MyGenerator,\n` +
565
+ ` ...\n` +
566
+ `});`
567
+ );
568
+ assert(
569
+ typeof generator.path === 'string',
570
+ `${chalk.red('path property is not a string')}\n` +
571
+ `When passing an object to Generator${chalk.cyan(
572
+ '#composeWith'
573
+ )} include the path to the generators files in the ${chalk.cyan(
574
+ 'path'
575
+ )} property\n\n` +
576
+ `this.composeWith({\n` +
577
+ ` ${chalk.yellow('path')}: '../my-generator',\n` +
578
+ ` ...\n` +
579
+ `});`
580
+ );
581
+ instantiatedGenerator = instantiate(generator.Generator, generator.path);
509
582
  }
510
583
 
511
584
  if (this._running) {
512
- generator.run();
585
+ instantiatedGenerator.run();
513
586
  } else {
514
- this._composedWith.push(generator);
587
+ this._composedWith.push(instantiatedGenerator);
515
588
  }
516
589
 
517
590
  return this;
@@ -522,7 +595,7 @@ class Generator extends EventEmitter {
522
595
  * @return {String} The name of the root generator
523
596
  */
524
597
  rootGeneratorName() {
525
- const pkg = readPkgUp.sync({cwd: this.resolved}).pkg;
598
+ const pkg = readPkgUp.sync({ cwd: this.resolved }).pkg;
526
599
  return pkg ? pkg.name : '*';
527
600
  }
528
601
 
@@ -531,7 +604,7 @@ class Generator extends EventEmitter {
531
604
  * @return {String} The version of the root generator
532
605
  */
533
606
  rootGeneratorVersion() {
534
- const pkg = readPkgUp.sync({cwd: this.resolved}).pkg;
607
+ const pkg = readPkgUp.sync({ cwd: this.resolved }).pkg;
535
608
  return pkg ? pkg.version : '0.0.0';
536
609
  }
537
610
 
@@ -673,7 +746,7 @@ class Generator extends EventEmitter {
673
746
  _writeFiles(done) {
674
747
  const self = this;
675
748
 
676
- const conflictChecker = through.obj(function (file, enc, cb) {
749
+ const conflictChecker = through.obj(function(file, enc, cb) {
677
750
  const stream = this;
678
751
 
679
752
  // If the file has no state requiring action, move on
@@ -8,8 +8,10 @@ const Table = require('cli-table');
8
8
 
9
9
  exports.isBinary = (existingFilePath, newFileContents) => {
10
10
  const existingHeader = readChunk.sync(existingFilePath, 0, 512);
11
- return istextorbinary.isBinarySync(undefined, existingHeader) ||
12
- (newFileContents && istextorbinary.isBinarySync(undefined, newFileContents));
11
+ return (
12
+ istextorbinary.isBinarySync(undefined, existingHeader) ||
13
+ (newFileContents && istextorbinary.isBinarySync(undefined, newFileContents))
14
+ );
13
15
  };
14
16
 
15
17
  exports.diff = (existingFilePath, newFileContents) => {
@@ -32,17 +34,15 @@ exports.diff = (existingFilePath, newFileContents) => {
32
34
 
33
35
  sizeDiff += prettyBytes(Math.abs(existingStat.size - newFileContents.length));
34
36
 
35
- table.push([
36
- 'Size',
37
- prettyBytes(existingStat.size),
38
- prettyBytes(newFileContents.length),
39
- sizeDiff
40
- ], [
41
- 'Last modified',
42
- dateFormat(existingStat.mtime),
43
- '',
44
- ''
45
- ]);
37
+ table.push(
38
+ [
39
+ 'Size',
40
+ prettyBytes(existingStat.size),
41
+ prettyBytes(newFileContents.length),
42
+ sizeDiff
43
+ ],
44
+ ['Last modified', dateFormat(existingStat.mtime), '', '']
45
+ );
46
46
 
47
47
  return table.toString();
48
48
  };
@@ -103,7 +103,6 @@ class Conflicter {
103
103
  * @param {Object} file File object respecting this interface: { path, contents }
104
104
  * @param {Function} cb Callback receiving a status string ('identical', 'create',
105
105
  * 'skip', 'force')
106
- * @return {null} nothing
107
106
  */
108
107
  collision(file, cb) {
109
108
  const rfilepath = path.relative(process.cwd(), file.path);
@@ -132,8 +131,8 @@ class Conflicter {
132
131
  /**
133
132
  * Actual prompting logic
134
133
  * @private
135
- * @param {Object} file
136
- * @param {Function} cb
134
+ * @param {Object} file vinyl file object
135
+ * @param {Function} cb callback receiving the next action
137
136
  */
138
137
  _ask(file, cb) {
139
138
  const rfilepath = path.relative(process.cwd(), file.path);
@@ -141,23 +140,28 @@ class Conflicter {
141
140
  name: 'action',
142
141
  type: 'expand',
143
142
  message: `Overwrite ${rfilepath}?`,
144
- choices: [{
145
- key: 'y',
146
- name: 'overwrite',
147
- value: 'write'
148
- }, {
149
- key: 'n',
150
- name: 'do not overwrite',
151
- value: 'skip'
152
- }, {
153
- key: 'a',
154
- name: 'overwrite this and all others',
155
- value: 'force'
156
- }, {
157
- key: 'x',
158
- name: 'abort',
159
- value: 'abort'
160
- }]
143
+ choices: [
144
+ {
145
+ key: 'y',
146
+ name: 'overwrite',
147
+ value: 'write'
148
+ },
149
+ {
150
+ key: 'n',
151
+ name: 'do not overwrite',
152
+ value: 'skip'
153
+ },
154
+ {
155
+ key: 'a',
156
+ name: 'overwrite this and all others',
157
+ value: 'force'
158
+ },
159
+ {
160
+ key: 'x',
161
+ name: 'abort',
162
+ value: 'abort'
163
+ }
164
+ ]
161
165
  };
162
166
 
163
167
  // Only offer diff option for files
@@ -194,7 +198,7 @@ class Conflicter {
194
198
  result.action = 'force';
195
199
  }
196
200
 
197
- this.adapter.log[result.action](rfilepath);
201
+ this.adapter.log[result.action || 'force'](rfilepath);
198
202
  return cb(result.action);
199
203
  });
200
204
  }
@@ -3,7 +3,7 @@ const _ = require('lodash');
3
3
  const chalk = require('chalk');
4
4
 
5
5
  const deprecate = (message, fn) => {
6
- return function () {
6
+ return function() {
7
7
  deprecate.log(message);
8
8
  return fn.apply(this, arguments);
9
9
  };
@@ -25,7 +25,7 @@ deprecate.object = (message, object) => {
25
25
  continue;
26
26
  }
27
27
 
28
- mirror[name] = deprecate(msgTpl({name}), func);
28
+ mirror[name] = deprecate(msgTpl({ name }), func);
29
29
  }
30
30
 
31
31
  return mirror;
@@ -36,7 +36,9 @@ const getCheckboxDefault = (question, defaultValue) => {
36
36
  * @private
37
37
  */
38
38
  const getListDefault = (question, defaultValue) => {
39
- const choiceValues = question.choices.map(choice => typeof choice === 'object' ? choice.value : choice);
39
+ const choiceValues = question.choices.map(choice =>
40
+ typeof choice === 'object' ? choice.value : choice
41
+ );
40
42
  return choiceValues.indexOf(defaultValue);
41
43
  };
42
44
 
@@ -115,7 +117,7 @@ promptSuggestion.prefillQuestions = (store, questions) => {
115
117
 
116
118
  const storedValue = promptValues[question.name];
117
119
 
118
- if ((storedValue === undefined) || _.isFunction(question.choices)) {
120
+ if (storedValue === undefined || _.isFunction(question.choices)) {
119
121
  // Do not override prompt default when question.choices is a function,
120
122
  // since can't guarantee that the `storedValue` will even be in the returned choices
121
123
  return question;
@@ -52,7 +52,6 @@ class Storage {
52
52
 
53
53
  /**
54
54
  * Save a new object of values
55
- * @return {null}
56
55
  */
57
56
  save() {
58
57
  this._persist(this._store);
@@ -82,7 +81,7 @@ class Storage {
82
81
  * @return {*} val Whatever was passed in as val.
83
82
  */
84
83
  set(key, val) {
85
- assert(!_.isFunction(val), 'Storage value can\'t be a function');
84
+ assert(!_.isFunction(val), "Storage value can't be a function");
86
85
 
87
86
  const store = this._store;
88
87
 
@@ -99,7 +98,6 @@ class Storage {
99
98
  /**
100
99
  * Delete a key from the store and schedule a save.
101
100
  * @param {String} key The key under which the value is stored.
102
- * @return {null}
103
101
  */
104
102
  delete(key) {
105
103
  const store = this._store;
package/package.json CHANGED
@@ -1,24 +1,13 @@
1
1
  {
2
2
  "name": "yeoman-generator",
3
- "version": "2.0.5",
3
+ "version": "3.2.0",
4
4
  "description": "Rails-inspired generator system that provides scaffolding for your apps",
5
- "license": "BSD-2-Clause",
6
- "repository": "yeoman/generator",
7
5
  "homepage": "http://yeoman.io",
8
6
  "author": "Yeoman",
9
- "main": "lib",
10
- "engines": {
11
- "node": ">=4"
12
- },
13
- "scripts": {
14
- "pretest": "xo",
15
- "test": "nyc mocha",
16
- "doc": "jsdoc -c jsdoc.json",
17
- "coverage": "nyc report --reporter=text-lcov | coveralls"
18
- },
19
7
  "files": [
20
8
  "lib"
21
9
  ],
10
+ "main": "lib",
22
11
  "keywords": [
23
12
  "development",
24
13
  "dev",
@@ -32,62 +21,96 @@
32
21
  "yeoman",
33
22
  "app"
34
23
  ],
24
+ "devDependencies": {
25
+ "coveralls": "^3.0.2",
26
+ "eslint": "^5.10.0",
27
+ "eslint-config-prettier": "^3.3.0",
28
+ "eslint-config-xo": "^0.25.1",
29
+ "eslint-plugin-prettier": "^3.0.0",
30
+ "husky": "^1.2.1",
31
+ "inquirer": "^6.0.0",
32
+ "jsdoc": "^3.5.5",
33
+ "lint-staged": "^8.1.0",
34
+ "mocha": "^5.1.1",
35
+ "mockery": "^2.1.0",
36
+ "nock": "^10.0.4",
37
+ "nyc": "^13.1.0",
38
+ "prettier": "^1.15.3",
39
+ "proxyquire": "^2.0.1",
40
+ "sinon": "^7.2.2",
41
+ "tui-jsdoc-template": "^1.2.2",
42
+ "yeoman-assert": "^3.1.1",
43
+ "yeoman-test": "^1.8.0"
44
+ },
45
+ "license": "BSD-2-Clause",
46
+ "repository": "yeoman/generator",
47
+ "engines": {
48
+ "node": ">=6"
49
+ },
50
+ "scripts": {
51
+ "pretest": "eslint .",
52
+ "test": "nyc mocha",
53
+ "doc": "jsdoc -c jsdoc.json",
54
+ "coverage": "nyc report --reporter=text-lcov | coveralls",
55
+ "precommit": "lint-staged"
56
+ },
35
57
  "dependencies": {
36
58
  "async": "^2.6.0",
37
59
  "chalk": "^2.3.0",
38
60
  "cli-table": "^0.3.1",
39
61
  "cross-spawn": "^6.0.5",
40
- "dargs": "^5.1.0",
62
+ "dargs": "^6.0.0",
41
63
  "dateformat": "^3.0.3",
42
- "debug": "^3.1.0",
64
+ "debug": "^4.1.0",
43
65
  "detect-conflict": "^1.0.0",
44
66
  "error": "^7.0.2",
45
- "find-up": "^2.1.0",
67
+ "find-up": "^3.0.0",
46
68
  "github-username": "^4.0.0",
47
69
  "istextorbinary": "^2.2.1",
48
70
  "lodash": "^4.17.10",
49
71
  "make-dir": "^1.1.0",
50
- "mem-fs-editor": "^4.0.0",
72
+ "mem-fs-editor": "^5.0.0",
51
73
  "minimist": "^1.2.0",
52
- "pretty-bytes": "^4.0.2",
53
- "read-chunk": "^2.1.0",
54
- "read-pkg-up": "^3.0.0",
74
+ "pretty-bytes": "^5.1.0",
75
+ "read-chunk": "^3.0.0",
76
+ "read-pkg-up": "^4.0.0",
55
77
  "rimraf": "^2.6.2",
56
78
  "run-async": "^2.0.0",
57
79
  "shelljs": "^0.8.0",
58
80
  "text-table": "^0.2.0",
59
- "through2": "^2.0.0",
81
+ "through2": "^3.0.0",
60
82
  "yeoman-environment": "^2.0.5"
61
83
  },
62
- "devDependencies": {
63
- "coveralls": "^3.0.0",
64
- "inquirer": "^5.2.0",
65
- "jsdoc": "^3.5.5",
66
- "mocha": "^5.1.1",
67
- "mockery": "^2.1.0",
68
- "nock": "^9.1.6",
69
- "nsp": "^3.2.1",
70
- "nyc": "^11.4.1",
71
- "proxyquire": "^2.0.1",
72
- "sinon": "^4.5.0",
73
- "tui-jsdoc-template": "^1.2.2",
74
- "xo": "^0.20.3",
75
- "yeoman-assert": "^3.1.1",
76
- "yeoman-test": "^1.7.0"
84
+ "lint-staged": {
85
+ "*.js": [
86
+ "eslint --fix",
87
+ "git add"
88
+ ],
89
+ "*.json": [
90
+ "prettier --write",
91
+ "git add"
92
+ ]
77
93
  },
78
- "xo": {
79
- "space": true,
94
+ "eslintConfig": {
95
+ "extends": [
96
+ "xo",
97
+ "prettier"
98
+ ],
99
+ "env": {
100
+ "mocha": true,
101
+ "node": true
102
+ },
80
103
  "rules": {
81
- "promise/no-callback-in-promise": "off"
104
+ "prettier/prettier": [
105
+ "error",
106
+ {
107
+ "singleQuote": true,
108
+ "printWidth": 90
109
+ }
110
+ ]
82
111
  },
83
- "overrides": [
84
- {
85
- "files": "test/**",
86
- "envs": [
87
- "node",
88
- "mocha"
89
- ]
90
- }
112
+ "plugins": [
113
+ "prettier"
91
114
  ]
92
115
  }
93
116
  }