Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6, new way to extend YO generator #161

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
12 changes: 12 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": ["eslint:recommended"],
"env": {
"node": true,
"es6": true,
"mocha": true
},
"rules": {
"no-var": "error",
"semi": ["error", "always"]
}
}
227 changes: 127 additions & 100 deletions generators/api/index.js
Original file line number Diff line number Diff line change
@@ -1,168 +1,187 @@
'use strict';
var path = require('path');
var yeoman = require('yeoman-generator');
var pluralize = require('pluralize');
var _ = require('lodash');
var recast = require('recast');
var reservedWords = require('reserved-words');

module.exports = yeoman.Base.extend({
prompting: function () {
var srcDir = this.config.get('srcDir') || 'src';
var apiDir = this.config.get('apiDir') || 'api';
var authMethods = this.config.get('authMethods') || [];

var methods = [
{name: 'Create (POST)', value: 'POST'},
{name: 'Retrieve list (GET)', value: 'GET LIST'},
{name: 'Retrieve one (GET)', value: 'GET ONE'},
{name: 'Update (PUT)', value: 'PUT'},
{name: 'Delete (DELETE)', value: 'DELETE'}
const path = require('path');
const generator = require('yeoman-generator');
const pluralize = require('pluralize');
const { camelCase, findLastIndex, findIndex, upperFirst, lowerCase } = require('lodash');
const recast = require('recast');
const reservedWords = require('reserved-words');

module.exports = class extends generator {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since generator is a class, let's make it capitalized: Generator


prompting () {
const srcDir = this.config.get('srcDir') || 'src';
const apiDir = this.config.get('apiDir') || 'api';
const authMethods = this.config.get('authMethods') || [];

const methods = [
{ name: 'Create (POST)', value: 'POST' },
{ name: 'Retrieve list (GET)', value: 'GET LIST' },
{ name: 'Retrieve one (GET)', value: 'GET ONE' },
{ name: 'Update (PUT)', value: 'PUT' },
{ name: 'Delete (DELETE)', value: 'DELETE' }
];

var getSelectedMethods = function (props) {
return methods.filter(function (method) {
const getSelectedMethods = (props) => {
return methods.filter((method) => {
return props.methods.indexOf(method.value) !== -1;
});
};

var prompts = [{
const prompts = [{
type: 'input',
name: 'kebab',
message: 'What\'s the API name?',
default: 'some-entity'
}, {
},
{
type: 'input',
name: 'lowerSuffix',
message: 'Name is a reserved word, add suffix for lowercase identifier',
default: 'Obj',
when: function (props) {
return reservedWords.check(_.lowerCase(props.kebab), 6);
when (props) {
return reservedWords.check(lowerCase(props.kebab), 6);
}
}, {
},
{
type: 'input',
name: 'kebabs',
message: 'What\'s the endpoint name?',
default: function (props) {
default (props) {
return pluralize(props.kebab);
}
}, {
},
{
type: 'input',
name: 'dir',
message: 'Where to put the code?',
default: srcDir + '/' + apiDir
}, {
},
{
type: 'checkbox',
name: 'methods',
message: 'Which methods it will have?',
default: methods.map(function (method) {
default: methods.map((method) => {
return method.value;
}),
choices: methods.map(function (method) {
return _.assign({}, method, {checked: true});
choices: methods.map((method) => {
return Object.assign({}, method, {checked: true});
})
}, {
},
{
type: 'checkbox',
name: 'masterMethods',
message: 'Which methods are protected by the master key?',
choices: getSelectedMethods,
default: [],
when: function () {
when () {
return authMethods.length;
}
}, {
},
{
type: 'checkbox',
name: 'adminMethods',
message: 'Which methods are only accessible by authenticated admin users?',
default: [],
choices: function (props) {
var choices = getSelectedMethods(props);
return choices.map(function (choice) {
choices (props) {
const choices = getSelectedMethods(props);
return choices.map((choice) => {
if (props.masterMethods.indexOf(choice.value) !== -1) {
return _.assign({}, choice, {disabled: 'Accessible only with master key'});
return Object.assign({}, choice, {disabled: 'Accessible only with master key'});
}
return choice;
});
},
when: function () {
when () {
return authMethods.length;
}
}, {
},
{
type: 'checkbox',
name: 'userMethods',
message: 'Which methods are only accessible by authenticated users?',
default: [],
choices: function (props) {
var choices = getSelectedMethods(props);
return choices.map(function (choice) {
choices (props) {

const choices = getSelectedMethods(props);

return choices.map((choice) => {

if (props.masterMethods.indexOf(choice.value) !== -1) {
return _.assign({}, choice, {disabled: 'Accessible only with master key'});
} else if (props.adminMethods.indexOf(choice.value) !== -1) {
return _.assign({}, choice, {disabled: 'Accessible only by admin users'});
return Object.assign({}, choice, {disabled: 'Accessible only with master key'});
}
else if (props.adminMethods.indexOf(choice.value) !== -1) {
return Object.assign({}, choice, {disabled: 'Accessible only by admin users'});
}

return choice;

});
},
when: function () {
when () {
return authMethods.length;
}
}, {
},
{
type: 'confirm',
name: 'generateModel',
message: 'Do you want to generate a model?',
default: true
}, {
},
{
type: 'input',
name: 'modelFields',
message: 'Which fields the model will have? (comma separated, do not include id)',
when: function (props) {
when (props) {
return props.generateModel;
}
}, {
},
{
type: 'confirm',
name: 'storeUser',
default: true,
message: function (props) {
message (props) {
return 'Do you want to store in a field the user who created the ' + props.kebab + '?';
},
when: function (props) {
when (props) {
return props.generateModel && props.userMethods && props.userMethods.indexOf('POST') !== -1;
}
}, {
},
{
type: 'input',
name: 'userField',
message: 'What\'s the name of the field which will store the user?',
default: 'user',
when: function (props) {
when (props) {
return props.storeUser;
}
}, {
},
{
type: 'confirm',
name: 'getList',
message: 'Do you want the retrieve methods (GET) to have the form { rows, count } ?',
default: false,
when: function (props) {
var methods = getSelectedMethods(props);
return methods.find(function (method) {
when (props) {
const methods = getSelectedMethods(props);

return methods.find((method) => {
return method.value === 'GET LIST';
}) && props.generateModel;
}
}];

return this.prompt(prompts).then(function (props) {
return this.prompt(prompts).then((props) => {
this.props = props;
this.props.camel = _.camelCase(this.props.kebab);
this.props.camels = pluralize(this.props.camel);
this.props.pascal = _.upperFirst(this.props.camel);
this.props.pascals = _.upperFirst(this.props.camels);
this.props.lower = _.lowerCase(this.props.camel);
this.props.lowers = _.lowerCase(this.props.camels);
this.props.start = _.upperFirst(this.props.lower);
this.props.starts = _.upperFirst(this.props.lowers);
this.props.camel = camelCase(this.props.kebab);
this.props.camels = pluralize(this.props.camel);
this.props.pascal = upperFirst(this.props.camel);
this.props.pascals = upperFirst(this.props.camels);
this.props.lower = lowerCase(this.props.camel);
this.props.lowers = lowerCase(this.props.camels);
this.props.start = upperFirst(this.props.lower);
this.props.starts = upperFirst(this.props.lowers);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that pass eslint? I don't think we need those alignments.

Copy link
Collaborator Author

@maustand maustand Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can create our custom rules...
Currently i am using eslint recommend, but we could use another rules about indentation, spaces, ect..

feel free to modify it.


// append suffix so we don't get reserved word clashes
if (this.props.lowerSuffix) {
this.props.camel = _.lowerCase(this.props.camel) + this.props.lowerSuffix;
this.props.camel = lowerCase(this.props.camel) + this.props.lowerSuffix;
}

this.props.authMethods = authMethods;
Expand All @@ -171,26 +190,26 @@ module.exports = yeoman.Base.extend({

this.props.modelFields = this.props.modelFields || '';
this.props.modelFields = this.props.modelFields ?
this.props.modelFields.split(',').map(function (field) {
return field.trim();
}) : [];
this.props.modelFields.split(',').map((field) => {
return field.trim();
}) : [];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorten:
this.props.modelFields.split(',').map(field => field.trim())


this.props.getList = props.getList || false;
this.props.storeUser = this.props.storeUser || false;

if (props.userField && this.props.modelFields.indexOf(props.userField) !== -1) {
this.props.modelFields.splice(this.props.modelFields.indexOf(props.userField), 1);
}
}.bind(this));
},

writing: function () {
var props = this.props;
var routesFile = path.join(props.dir, 'index.js');
var copyTpl = this.fs.copyTpl.bind(this.fs);
var tPath = this.templatePath.bind(this);
var dPath = this.destinationPath.bind(this);
var filepath = function (filename) {
});
}
writing () {

const props = this.props;
const routesFile = path.join(props.dir, 'index.js');
const copyTpl = this.fs.copyTpl.bind(this.fs);
const tPath = this.templatePath.bind(this);
const dPath = this.destinationPath.bind(this);
const filepath = (filename) => {
return path.join(props.dir, props.kebab, filename);
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make it shorter too: filename => path.join(...)


Expand All @@ -204,43 +223,51 @@ module.exports = yeoman.Base.extend({
}

if (this.fs.exists(routesFile)) {
var ast = recast.parse(this.fs.read(routesFile));
var body = ast.program.body;
var lastImportIndex = _.findLastIndex(body, function (statement) {

const ast = recast.parse(this.fs.read(routesFile));
const body = ast.program.body;
const lastImportIndex = findLastIndex(body, (statement) => {
return statement.type === 'ImportDeclaration';
});
var actualImportCode = recast.print(body[lastImportIndex]).code;
var importString = ['import ', props.camel, ' from \'./', props.kebab, '\''].join('');
const actualImportCode = recast.print(body[lastImportIndex]).code;
const importString = ['import ', props.camel, ' from \'./', props.kebab, '\''].join('');

body.splice(lastImportIndex, 1, importString);
body.splice(lastImportIndex, 0, actualImportCode);

var middlewareString = [
'router.use(\'/', props.kebabs, '\', ', props.camel, ')'
].join('');
var lastMiddlewareIndex = _.findLastIndex(body, function (statement) {
const middlewareString = ['router.use(\'/', props.kebabs, '\', ', props.camel, ')'].join('');

const lastMiddlewareIndex = findLastIndex(body, function (statement) {
if (!statement.expression || !statement.expression.callee) {
return false;
}
var callee = statement.expression.callee;

const callee = statement.expression.callee;
return callee.object.name === 'router' && callee.property.name === 'use';

});

if (lastMiddlewareIndex === -1) {
var exportRouterIndex = _.findIndex(body, function (statement) {

const exportRouterIndex = findIndex(body, function (statement) {
return statement.type === 'ExportDefaultDeclaration';
});

body.splice(exportRouterIndex, 0, middlewareString);
} else {
var actualMiddlewareCode = recast.print(body[lastMiddlewareIndex]).code;
}
else {

const actualMiddlewareCode = recast.print(body[lastMiddlewareIndex]).code;

body.splice(lastMiddlewareIndex, 1, middlewareString);
body.splice(lastMiddlewareIndex, 0, actualMiddlewareCode);
}

this.fs.write(routesFile, recast.print(ast).code);
}
},
}

install: function () {
install () {

}
});
};
Loading