Skip to content

Commit

Permalink
Merge 2b3b25d into d946611
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed Jun 22, 2020
2 parents d946611 + 2b3b25d commit cb46971
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 3 deletions.
90 changes: 87 additions & 3 deletions lib/index.js
Expand Up @@ -149,6 +149,7 @@ class Generator extends EventEmitter {
this._args = args || [];
this._options = {};
this._arguments = [];
this._prompts = [];
this._composedWith = [];
this._transformStreams = [];
this._namespace = this.options.namespace;
Expand Down Expand Up @@ -441,6 +442,48 @@ class Generator extends EventEmitter {
this._debug(...args);
}

/**
* Register stored config prompts and optional option alternative.
*
* @param {Inquirer|Inquirer[]} questions - Inquirer question or questions.
* @param {Object|Boolean} [questions.exportOption] - Additional data to export this question as an option.
* @param {Storage|String} [question.storage=this.config] - Storage to store the answers.
*/
registerConfigPrompts(questions) {
questions = Array.isArray(questions) ? questions : [questions];
const getOptionTypeFromInquirerType = type => {
if (type === 'number') {
return Number;
}

if (type === 'confirm') {
return Boolean;
}

if (type === 'checkbox') {
return Array;
}

return String;
};

questions.forEach(q => {
const question = { ...q };
if (q.exportOption) {
let option = typeof q.exportOption === 'boolean' ? {} : q.exportOption;
this.option({
name: q.name,
type: getOptionTypeFromInquirerType(q.type),
description: q.message,
...option,
storage: q.storage || this.config
});
}

this._prompts.push(question);
});
}

/**
* Prompt user to answer questions. The signature of this method is the same as {@link https://github.com/SBoudrias/Inquirer.js Inquirer.js}
*
Expand Down Expand Up @@ -468,7 +511,9 @@ class Generator extends EventEmitter {
const storageForQuestion = {};

const getAnswerFromStorage = function(question) {
const questionStorage = question.storage || storage;
let questionStorage = question.storage || storage;
questionStorage =
typeof questionStorage === 'string' ? this[questionStorage] : questionStorage;
if (questionStorage) {
checkInquirer();

Expand Down Expand Up @@ -530,16 +575,29 @@ class Generator extends EventEmitter {
* generate generator usage. By default, generators get all the cli options
* parsed by nopt as a `this.options` hash object.
*
* @param {String} name - Option name
* @param {String} [name] - Option name
* @param {Object} config - Option options
* @param {any} config.type - Either Boolean, String or Number
* @param {string} [config.description] - Description for the option
* @param {any} [config.default] - Default value
* @param {any} [config.alias] - Option name alias (example `-h` and --help`)
* @param {any} [config.hide] - Boolean whether to hide from help
* @param {Storage} [config.storage] - Storage to persist the option
* @return {this} This generator
*/
option(name, config) {
if (Array.isArray(name)) {
name.forEach(option => {
this.option(option);
});
return;
}

if (typeof name === 'object') {
config = name;
name = config.name;
}

config = config || {};

// Alias default to defaults for backward compatibility.
Expand Down Expand Up @@ -578,6 +636,12 @@ class Generator extends EventEmitter {
}

this.parseOptions();
if (config.storage && this.options[name] !== undefined) {
const storage =
typeof config.storage === 'string' ? this[config.storage] : config.storage;
storage.set(name, this.options[name]);
}

return this;
}

Expand Down Expand Up @@ -828,14 +892,34 @@ class Generator extends EventEmitter {

const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const validMethods = methods.filter(methodIsValid);
if (!validMethods.length) {
if (validMethods.length === 0 && this._prompts.length === 0) {
const error = new Error(
'This Generator is empty. Add at least one method for it to run.'
);
this.emit('error', error);
throw error;
}

if (this._prompts.length > 0) {
this.queueTask({
method: () => this.prompt(this._prompts, this.config),
taskName: 'Prompt registered questions',
queueName: 'prompting',
cancellable: true
});

if (validMethods.length === 0) {
this.queueTask({
method: () => {
this.renderTemplate();
},
taskName: 'Empty generator: copy templates',
queueName: 'writing',
cancellable: true
});
}
}

validMethods.forEach(methodName => this.queueOwnTask(methodName, taskOptions));
}

Expand Down
48 changes: 48 additions & 0 deletions test/base.js
Expand Up @@ -774,6 +774,54 @@ describe('Base', () => {
});
});

describe('#registerConfigPrompts()', () => {
it('adds an prompt with common definitions', function() {
this.dummy.registerConfigPrompts({
name: 'foo',
message: 'bar',
type: 'number',
prompt: true
});
assert.equal(this.dummy._prompts[0].name, 'foo');
assert.equal(this.dummy._prompts[0].message, 'bar');
assert.equal(this.dummy._prompts[0].type, 'number');
});

it('should export option', function() {
this.dummy.registerConfigPrompts({
name: 'foo',
message: 'bar2',
type: 'string',
exportOption: true
});
assert.equal(this.dummy._prompts[0].name, 'foo');
assert.equal(this.dummy._prompts[0].message, 'bar2');
assert.equal(this.dummy._prompts[0].type, 'string');
assert.equal(this.dummy._options.foo.name, 'foo');
assert.equal(this.dummy._options.foo.description, 'bar2');
assert.equal(this.dummy._options.foo.type, String);
});

it('allows to customize option config', function() {
this.dummy.registerConfigPrompts({
name: 'foo',
message: 'bar2',
type: 'string',
exportOption: {
description: 'bar3',
name: 'foo2',
type: Number
}
});
assert.equal(this.dummy._prompts[0].name, 'foo');
assert.equal(this.dummy._prompts[0].message, 'bar2');
assert.equal(this.dummy._prompts[0].type, 'string');
assert.equal(this.dummy._options.foo2.name, 'foo2');
assert.equal(this.dummy._options.foo2.description, 'bar3');
assert.equal(this.dummy._options.foo2.type, Number);
});
});

describe('#parseOptions()', () => {
beforeEach(function() {
this.dummy = new this.Dummy(['start', '--foo', 'bar', '-s', 'baz', 'remain'], {
Expand Down
23 changes: 23 additions & 0 deletions test/fixtures/generator-defaults/app/index.js
@@ -0,0 +1,23 @@
'use strict';

// Example of a generator with options.
//
// It takes a list of arguments (usually CLI args) and a Hash of options
// (CLI options), the context of the function is a `new Generator.Base`
// object, which means that you can use the API as if you were extending
// `Base`.

var Base = require('../../../../');

module.exports = class extends Base {
constructor(args, opts) {
super(args, opts);

console.log(require('./options'));
this.option(require('./options'));

this.registerConfigPrompts(require('./prompts'));
}
};

module.exports.namespace = 'options:generator';
3 changes: 3 additions & 0 deletions test/fixtures/generator-defaults/app/options.js
@@ -0,0 +1,3 @@
module.exports = [
{ name: 'extra', type: String, storage: 'config' }
];
9 changes: 9 additions & 0 deletions test/fixtures/generator-defaults/app/prompts.js
@@ -0,0 +1,9 @@
module.exports = [
{
name: 'foo',
type: 'input',
message: 'Test foo value',
default: 'bar',
exportOption: true
}
];
@@ -0,0 +1,2 @@
var <%= foo %> = '<%= foo %>';
<%= extra %>
12 changes: 12 additions & 0 deletions test/fixtures/generator-defaults/package.json
@@ -0,0 +1,12 @@
{
"author": "",
"name": "generator-defaults",
"version": "0.0.0",
"main": "main.js",
"dependencies": {},
"devDependencies": {},
"optionalDependencies": {},
"engines": {
"node": "*"
}
}
32 changes: 32 additions & 0 deletions test/integration.js
@@ -0,0 +1,32 @@
'use strict';
const fs = require('fs');
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');

describe('Integration', () => {
let content;
before(function() {
this.timeout(5000);
let tmpDir;
return helpers
.create(path.join(__dirname, 'fixtures/generator-defaults/app'))
.inTmpDir(() => {
tmpDir = process.cwd();
})
.withPrompts({ foo: 'fooValue' })
.withOptions({ extra: 'extraValue' })
.build()
.run()
.then(() => {
const file = path.join(tmpDir, 'foo-template.js');
content = fs.readFileSync(path.resolve(file)).toString();
});
});
it('writes prompt value to foo-template.js', () => {
assert(content.includes("fooValue = 'fooValue"));
});
it('writes option value foo-template.js', () => {
assert(content.includes('extraValue'));
});
});

0 comments on commit cb46971

Please sign in to comment.