yeoman-generator
Version:
Rails-inspired generator system that provides scaffolding for your apps
180 lines (154 loc) • 5.15 kB
JavaScript
;
var path = require('path');
var fs = require('fs');
var _ = require('lodash');
var glob = require('glob');
var debug = require('debug')('generators:environment');
var win32 = process.platform === 'win32';
/**
* @mixin
* @alias env/resolver
*/
var resolver = module.exports;
/**
* Search for generators and their sub generators.
*
* A generator is a `:lookup/:name/index.js` file placed inside an NPM module.
*
* Defaults lookups are:
* - ./
* - generators/
* - lib/generators/
*
* So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
* registered as `dummy:yo` generator.
*/
resolver.lookup = function () {
var generatorsModules = this.findGeneratorsIn(this.getNpmPaths());
var patterns = [];
this.lookups.forEach(function (lookup) {
generatorsModules.forEach(function (modulePath) {
patterns.push(path.join(modulePath, lookup) + '/*/index.js');
});
});
patterns.forEach(function (pattern) {
glob.sync(pattern).forEach(function (filename) {
this._tryRegistering(filename);
}, this);
}, this);
};
/**
* Search NPM for every available generators.
* Generators are NPM modules who's name start with `generator-` and who're placed in the
* top level `node_module` path. They can be installed globally or locally.
*
* @param {Array} List of search paths
* @return {Array} List of the generator modules path
*/
resolver.findGeneratorsIn = function (searchPaths) {
var modules = [];
searchPaths.forEach(function (root) {
if (!root) return;
var found = glob.sync('generator-*', { cwd: root, stat: true }).map(function (match) {
return path.join(root, match);
});
modules = found.concat(modules);
});
return modules;
};
/**
* Try registering a Generator to this environment.
* @param {String} generatorReference A generator reference, usually a file path.
*/
resolver._tryRegistering = function (generatorReference) {
var namespace;
var path = fs.realpathSync(generatorReference);
try {
debug('found %s, trying to register', generatorReference);
if (path !== generatorReference) {
namespace = this.namespace(generatorReference);
}
this.register(path, namespace);
} catch (e) {
console.error('Unable to register %s (Error: %s)', generatorReference, e.message);
}
};
/**
* Get the NPM lookup directories (`node_modules/`)
* @return {Array} lookup paths
*/
resolver.getNpmPaths = function () {
var paths = [];
// Walk up the CWD and add `node_modules/` folder lookup on each level
process.cwd().split(path.sep).forEach(function (part, i, parts) {
var lookup = path.join.apply(path, parts.slice(0, i + 1).concat(['node_modules']));
if (!win32) {
lookup = '/' + lookup;
}
paths.push(lookup);
});
// Adding global NPM directories
// We tried using NPM to get the global modules path, but it haven't work out
// because of bugs in the parseable implementation of `ls` command and mostly
// performance issues. So, we go with our best bet for now.
if (process.env.NODE_PATH) {
paths = _.compact(process.env.NODE_PATH.split(path.delimiter)).concat(paths);
} else {
// global node_modules should be 5 directory up this one (most of the time)
paths.push(path.join(__dirname, '../../../../..'));
// adds support for generator resolving when yeoman-generator has been linked
paths.push(path.join(path.dirname(process.argv[1]), '../..'));
// Default paths for each system
if (win32) {
paths.push(path.join(process.env.APPDATA, 'npm/node_modules'));
} else {
paths.push('/usr/lib/node_modules');
}
}
return paths.reverse();
};
/**
* Get or create an alias.
*
* Alias allows the `get()` and `lookup()` methods to search in alternate
* filepath for a given namespaces. It's used for example to map `generator-*`
* npm package to their namespace equivalent (without the generator- prefix),
* or to default a single namespace like `angular` to `angular:app` or
* `angular:all`.
*
* Given a single argument, this method acts as a getter. When both name and
* value are provided, acts as a setter and registers that new alias.
*
* If multiple alias are defined, then the replacement is recursive, replacing
* each alias in reverse order.
*
* An alias can be a single String or a Regular Expression. The finding is done
* based on .match().
*
* @param {String|RegExp} match
* @param {String} value
*
* @example
*
* env.alias(/^([a-zA-Z0-9:\*]+)$/, 'generator-$1');
* env.alias(/^([^:]+)$/, '$1:app');
* env.alias(/^([^:]+)$/, '$1:all');
* env.alias('foo');
* // => generator-foo:all
*/
resolver.alias = function alias(match, value) {
if (match && value) {
this.aliases.push({
match: match instanceof RegExp ? match : new RegExp('^' + match + '$'),
value: value
});
return this;
}
var aliases = this.aliases.slice(0).reverse();
return aliases.reduce(function (res, alias) {
if (!alias.match.test(res)) {
return res;
}
return res.replace(alias.match, alias.value);
}, match);
};