generator-nitro
Version:
Yeoman generator for the nitro frontend framework
255 lines (223 loc) • 8.23 kB
JavaScript
/**
* this generator is used as subgenerator nitro:pattern (and nitro:component)
*/
/* eslint-disable no-inline-comments, max-len, complexity, global-require, require-jsdoc */
const Generator = require('yeoman-generator');
const clc = require('cli-color');
const yosay = require('yosay');
const path = require('path');
const fs = require('fs');
const gitconfig = require('git-config');
const { globSync } = require('glob');
const _ = require('lodash');
module.exports = class extends Generator {
constructor(args, opts) {
// Calling the super constructor
// eslint-disable-next-line prefer-rest-params
super(args, opts);
// Pattern name
this.argument('name', { desc: 'the name of your pattern?', type: String, required: false, defaults: '' });
this.passedInOptions = {
name: this.options.name,
type: this.options.type,
modifier: this.options.modifier,
decorator: this.options.decorator,
};
// get project config
let projectConfig = {};
if (fs.existsSync(this.destinationPath('config/default.js'))) {
// use config from 2.x
process.env.NODE_CONFIG_DIR = this.destinationPath('config');
projectConfig = require('config');
} else if (fs.existsSync(this.destinationPath('config.json'))) {
// use config.json from 1.x
projectConfig = require(this.destinationPath('config.json'));
}
// Compatibility with older config and subgeneartor nitro:component
const _patternName = this.options.namespace.split(':')[1];
this._pattern = {
name: _patternName,
Name: _.upperFirst(_patternName),
node: projectConfig.nitro.patterns || projectConfig.nitro.components,
};
this.types = _.map(this._pattern.node, (value, key) => {
return key;
});
this.option('name', {
desc: `the name of your ${this._pattern.Name}`,
type: String,
defaults: this.passedInOptions.name || '',
});
this.option('type', {
desc: `your desired type [${this.types.join('|')}]`,
type: String,
defaults: this.passedInOptions.type || this.types[0],
});
// Pattern modifier
this.option('modifier', {
desc: 'the name of your modifier',
type: String,
defaults: this.passedInOptions.modifier || '',
});
// Pattern decorator
this.option('decorator', {
desc: 'the name of your decorator',
type: String,
defaults: this.passedInOptions.decorator || '',
});
}
initializing() {
this.pkg = require('../../package.json');
}
prompting() {
this.log(yosay(`Let me help you to create your ${this._pattern.name}…`));
return this.prompt([
{
name: 'name',
message: `What's the name of your ${this._pattern.name}?`,
default: this.options.name,
validate: (value) => {
if (!_.isString(value) || _.isEmpty(value)) {
return `${this._pattern.Name} name has to be a valid string`;
}
if (/^[0-9]/.test(value)) {
return `${this._pattern.Name} name must not start with a Number`;
}
if (/(component|pattern|modifier|decorator)/i.test(value)) {
return `Avoid 'pattern', 'component', 'modifier' and 'decorator' in ${this._pattern.Name} names. These have a special meaning`;
}
return true;
},
},
{
name: 'type',
type: 'list',
message: "And what's your desired type?",
choices: this.types,
default: _.indexOf(this.types, this.options.type) || 0,
},
{
name: 'modifier',
message: 'Would you like to create a CSS modifier? Type your desired name or leave empty.',
default: this.options.modifier || '',
validate: (value) => {
if (/(component|pattern|modifier|decorator)/i.test(value)) {
return `Avoid 'pattern', 'component', 'modifier' and 'decorator' in ${this._pattern.Name} modifier names. These have a special meaning`;
}
return true;
},
},
{
name: 'decorator',
message: 'Would you like to create a JS decorator? Type your desired name or leave empty.',
default: this.options.decorator || '',
validate: (value) => {
if (_.isString(value) && /^[0-9]/.test(value)) {
return `${this._pattern.Name} decorator must not start with a Number`;
}
if (/(component|pattern|modifier|decorator)/i.test(value)) {
return `Avoid 'pattern', 'component', 'modifier' and 'decorator' in ${this._pattern.Name} decorator names. These have a special meaning`;
}
return true;
},
},
]).then((answers) => {
this.name = answers.name;
this.options.type = answers.type;
this.options.modifier = answers.modifier;
this.options.decorator = answers.decorator;
});
}
writing() {
const hasModifier = !_.isEmpty(this.options.modifier);
const hasDecorator = !_.isEmpty(this.options.decorator);
let msg = `Creating ${clc.cyan(this.name)} ${this.options.type}`;
if (hasModifier || hasDecorator) {
msg += ' with ';
}
if (hasModifier) {
msg += `CSS modifier ${clc.cyan(this.options.modifier)}`;
if (hasDecorator) {
msg += ' and ';
}
}
if (hasDecorator) {
msg += `JS decorator ${clc.cyan(this.options.decorator)}`;
}
this.log(msg);
const pattern = this._pattern.node[this.options.type];
const folder = this.name.replace(/[^A-Za-z0-9-]/g, '');
const files = globSync('**/*', { cwd: this.destinationPath(pattern.template), nodir: true, dot: true, posix: true });
const ignores = [
// files to ignore
'.DS_Store',
];
const user = {
name: '',
email: '',
};
const gitConfig = gitconfig.sync();
if (!_.isEmpty(gitConfig) && !_.isEmpty(gitConfig.user)) {
user.name = gitConfig.user.name;
user.email = gitConfig.user.email;
}
const patternReplacements = {
name: this.name, // Pattern name, eg. Main Navigation
folder, // Pattern folder, eg. MainNavigation
js: _.upperFirst(_.camelCase(this.name.replace(/^[0-9]+/, ''))), // Pattern name for use in JS files, eg. MainNavigation
css: _.kebabCase(this.name), // Pattern name for use in CSS files, eg. main-navigation
prefix: pattern.patternPrefix || pattern.componentPrefix || null, // CSS class prefix, eg. m
type: this.options.type, // Pattern type, eg. atom, molecule etc.
file: this.name.replace(/[^A-Za-z0-9-]/g, '').toLowerCase(), // Pattern filename, eg. mainnavigation
};
const replacements = {
user,
pattern: patternReplacements,
component: patternReplacements,
modifier: {
name: this.options.modifier, // Modifier name, eg. Highlight
css: _.kebabCase(this.options.modifier), // Modifier name for use in CSS files, eg. highlight
file: this.options.modifier.replace(/[^A-Za-z0-9-]/g, '').toLowerCase(), // Modifier filename, eg.highlight
},
decorator: {
name: this.options.decorator, // Decorator name, eg. Highlight
js: _.upperFirst(_.camelCase(this.options.decorator.replace(/^[0-9]+/, ''))), // Decorator name for use in JS files, eg. Highlight
file: this.options.decorator.replace(/[^A-Za-z0-9-]/g, '').toLowerCase(), // Decorator filename, eg.highlight
},
};
files.forEach((file) => {
if (_.indexOf(ignores, file) !== -1) {
return;
}
// exclude modifier files if modifier is not set
if (file.indexOf('modifier') !== -1) {
if (_.isEmpty(this.options.modifier)) {
return;
}
}
// exclude decorator files if decorator is not set
if (file.indexOf('decorator') !== -1) {
if (_.isEmpty(this.options.decorator)) {
return;
}
}
// filename replacements
const fileReplacements = {
$pattern$: replacements.pattern.file,
$component$: replacements.pattern.file,
$modifier$: replacements.modifier.file,
$decorator$: replacements.decorator.file,
};
let filename = file;
_.forOwn(fileReplacements, (value, key) => {
filename = path.join(path.dirname(filename), path.basename(filename).split(key).join(value));
});
this.fs.copyTpl(
this.destinationPath(`${pattern.template}/${file}`),
this.destinationPath(`${pattern.path}/${folder}/${filename}`),
replacements
);
}, this);
}
};
;