generator-pyhipster
Version:
Python (Flask) + Angular/React/Vue in one handy generator
389 lines (359 loc) • 12.1 kB
JavaScript
/**
* Copyright 2013-2022 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const assert = require('assert');
const chalk = require('chalk');
const _ = require('lodash');
const path = require('path');
const Environment = require('yeoman-environment');
const { CLI_NAME, logger } = require('./utils');
const { loadYoRc, packageNameToNamespace } = require('../generators/utils');
const { parseBlueprintInfo, loadBlueprintsFromConfiguration, mergeBlueprints } = require('../utils/blueprint');
const createEnvironment = (args, options = {}, adapter) => {
// Remove after migration to environment 3.
const configOptions = { sharedEntities: {} };
const sharedOptions = {
fromCli: true,
localConfigOnly: true,
...options.sharedOptions,
configOptions,
};
return Environment.createEnv(args, { newErrorHandler: true, ...options, sharedOptions }, adapter);
};
module.exports = class EnvironmentBuilder {
/**
* Creates a new EnvironmentBuilder with a new Environment.
*
* @param {...any} args - Arguments passed to Environment.createEnv().
* @return {EnvironmentBuilder} envBuilder
*/
static create(args, options = {}, adapter) {
const env = createEnvironment(args, options, adapter);
env.setMaxListeners(0);
return new EnvironmentBuilder(env);
}
/**
* Creates a new Environment with blueprints.
*
* Can be used to create a new test environment (requires yeoman-test >= 2.6.0):
* @example
* const promise = require('yeoman-test').create('jhipster:app', {}, {createEnv: EnvironmentBuilder.createEnv}).run();
*
* @param {...any} args - Arguments passed to Environment.createEnv().
* @return {EnvironmentBuilder} envBuilder
*/
static createEnv(...args) {
return EnvironmentBuilder.createDefaultBuilder(...args).getEnvironment();
}
/**
* Creates a new EnvironmentBuilder with a new Environment and load jhipster, blueprints and sharedOptions.
*
* @param {...any} args - Arguments passed to Environment.createEnv().
* @return {EnvironmentBuilder} envBuilder
*/
static createDefaultBuilder(...args) {
return EnvironmentBuilder.create(...args).prepare();
}
/**
* Class to manipulate yeoman environment for jhipster needs.
* - Registers jhipster generators.
* - Loads blueprints from argv and .yo-rc.json.
* - Installs blueprints if not found.
* - Loads sharedOptions.
*/
constructor(env) {
this.env = env;
}
prepare({ blueprints, lookups } = {}) {
this._lookupJHipster()._loadBlueprints(blueprints)._lookups(lookups)._lookupBlueprints()._loadSharedOptions();
return this;
}
getBlueprintsNamespaces() {
return Object.keys(this._blueprintsWithVersion).map(packageName => packageNameToNamespace(packageName));
}
/**
* Construct blueprint option value.
*
* @return {String}
*/
getBlueprintsOption() {
return Object.entries(this._blueprintsWithVersion)
.map(([packageName, packageVersion]) => (packageVersion ? `${packageName}@${packageVersion}` : packageName))
.join(',');
}
/**
* @private
* Lookup current jhipster generators.
*
* @return {EnvironmentBuilder} this for chaining.
*/
_lookupJHipster() {
// Register jhipster generators.
this.env.lookup({ packagePaths: [path.join(__dirname, '..')], lookups: ['generators'] }).forEach(generator => {
// Verify jhipster generators namespace.
assert(
generator.namespace.startsWith(`${CLI_NAME}:`),
`Error on the registered namespace ${generator.namespace}, make sure your folder is called generator-jhipster.`
);
});
return this;
}
_lookups(lookups = []) {
lookups = [].concat(lookups);
lookups.forEach(lookup => {
this.env.lookup(lookup);
});
return this;
}
/**
* @private
* Load blueprints from argv, .yo-rc.json.
*
* @return {EnvironmentBuilder} this for chaining.
*/
_loadBlueprints(blueprints) {
this._blueprintsWithVersion = {
...this._getAllBlueprintsWithVersion(),
...blueprints,
};
return this;
}
/**
* @private
* Lookup current loaded blueprints.
*
* @param {Object} [options] - forwarded to Environment lookup.
* @return {EnvironmentBuilder} this for chaining.
*/
_lookupBlueprints(options) {
const allBlueprints = Object.keys(this._blueprintsWithVersion);
if (allBlueprints && allBlueprints.length > 0) {
// Lookup for blueprints.
this.env.lookup({ ...options, filterPaths: true, packagePatterns: allBlueprints });
}
return this;
}
/**
* Lookup for generators.
*
* @param {string[]} [generators] - generators to lookup.
* @param {Object} [options] - forwarded to Environment lookup.
* @return {EnvironmentBuilder} this for chaining.
*/
lookupGenerators(generators, options = {}) {
this.env.lookup({ filterPaths: true, ...options, packagePatterns: generators });
return this;
}
/**
* @private
* Load sharedOptions from jhipster and blueprints.
*
* @return {EnvironmentBuilder} this for chaining.
*/
_loadSharedOptions() {
const blueprintsPackagePath = this._getBlueprintPackagePaths();
if (blueprintsPackagePath) {
const sharedOptions = this._getSharedOptions(blueprintsPackagePath) || {};
// Env will forward sharedOptions to every generator
Object.assign(this.env.sharedOptions, sharedOptions);
}
return this;
}
/**
* Get blueprints commands.
*
* @return {Object[]} blueprint commands.
*/
getBlueprintCommands() {
const blueprintsPackagePath = this._getBlueprintPackagePaths();
return this._getBlueprintCommands(blueprintsPackagePath);
}
/**
* Get the environment.
*
* @return {Environment} the yeoman environment.
*/
getEnvironment() {
return this.env;
}
/**
* @private
* Load blueprints from argv.
* At this point, commander has not parsed yet because we are building it.
* @returns {Blueprint[]}
*/
_getBlueprintsFromArgv() {
const blueprintNames = [];
const indexOfBlueprintArgv = process.argv.indexOf('--blueprint');
if (indexOfBlueprintArgv > -1) {
blueprintNames.push(process.argv[indexOfBlueprintArgv + 1]);
}
const indexOfBlueprintsArgv = process.argv.indexOf('--blueprints');
if (indexOfBlueprintsArgv > -1) {
blueprintNames.push(...process.argv[indexOfBlueprintsArgv + 1].split(','));
}
if (!blueprintNames.length) {
return [];
}
return blueprintNames.map(v => parseBlueprintInfo(v));
}
/**
* @private
* Load blueprints from .yo-rc.json.
* @returns {Blueprint[]}
*/
_getBlueprintsFromYoRc() {
const yoRc = loadYoRc();
if (!yoRc || !yoRc['generator-jhipster']) {
return [];
}
return loadBlueprintsFromConfiguration(yoRc['generator-jhipster']);
}
/**
* @private
* Creates a 'blueprintName: blueprintVersion' object from argv and .yo-rc.json blueprints.
*/
_getAllBlueprintsWithVersion() {
return mergeBlueprints(this._getBlueprintsFromArgv(), this._getBlueprintsFromYoRc()).reduce((acc, blueprint) => {
acc[blueprint.name] = blueprint.version;
return acc;
}, {});
}
/**
* @private
* Get packagePaths from current loaded blueprints.
*/
_getBlueprintPackagePaths() {
const blueprints = this._blueprintsWithVersion;
if (!blueprints || Object.keys(blueprints).length === 0) {
return undefined;
}
const blueprintsToInstall = Object.entries(blueprints)
.filter(([blueprint, _version]) => {
const namespace = packageNameToNamespace(blueprint);
if (!this.env.getPackagePath(namespace)) {
this.env.lookupLocalPackages(blueprint);
}
return !this.env.getPackagePath(namespace);
})
.reduce((acc, [blueprint, version]) => {
acc[blueprint] = version;
return acc;
}, {});
if (Object.keys(blueprintsToInstall).length > 0) {
this.env.installLocalGenerators(blueprintsToInstall);
}
return Object.entries(blueprints).map(([blueprint, _version]) => {
const namespace = packageNameToNamespace(blueprint);
const packagePath = this.env.getPackagePath(namespace);
if (!packagePath) {
logger.fatal(
`The ${chalk.yellow(blueprint)} blueprint provided is not installed. Please install it using command ${chalk.yellow(
`npm i -g ${blueprint}`
)}`
);
}
return [blueprint, packagePath];
});
}
/**
* @private
* Get blueprints commands.
*
* @return {Object[]} commands.
*/
_getBlueprintCommands(blueprintPackagePaths) {
if (!blueprintPackagePaths) {
return undefined;
}
let result;
blueprintPackagePaths.forEach(([blueprint, packagePath]) => {
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
try {
let blueprintCommand;
try {
blueprintCommand = require(`${packagePath}/cli/commands`);
} catch (e) {
blueprintCommand = require(`${packagePath}/cli/commands.cjs`);
}
const blueprintCommands = _.cloneDeep(blueprintCommand);
Object.entries(blueprintCommands).forEach(([_command, commandSpec]) => {
commandSpec.blueprint = commandSpec.blueprint || blueprint;
});
result = { ...result, ...blueprintCommands };
} catch (e) {
const msg = `No custom commands found within blueprint: ${blueprint} at ${packagePath}`;
/* eslint-disable no-console */
console.info(`${chalk.green.bold('INFO!')} ${msg}`);
}
});
return result;
}
/**
* @private
* Get blueprints sharedOptions.
*
* @return {Object} sharedOptions.
*/
_getSharedOptions(blueprintPackagePaths) {
function joiner(objValue, srcValue) {
if (objValue === undefined) {
return srcValue;
}
if (Array.isArray(objValue) && Array.isArray(srcValue)) {
return objValue.concat(srcValue);
}
if (Array.isArray(objValue)) {
return [...objValue, srcValue];
}
if (Array.isArray(srcValue)) {
return [objValue, ...srcValue];
}
return [objValue, srcValue];
}
function loadSharedOptionsFromFile(sharedOptionsFile, msg, errorMsg) {
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
try {
const opts = require(sharedOptionsFile);
/* eslint-disable no-console */
if (msg) {
console.info(`${chalk.green.bold('INFO!')} ${msg}`);
}
return opts;
} catch (e) {
if (errorMsg) {
console.info(`${chalk.green.bold('INFO!')} ${errorMsg}`);
}
}
return {};
}
const localPath = './.jhipster/sharedOptions';
let result = loadSharedOptionsFromFile(path.resolve(localPath), `SharedOptions found at local config ${localPath}`);
if (!blueprintPackagePaths) {
return undefined;
}
blueprintPackagePaths.forEach(([blueprint, packagePath]) => {
const errorMsg = `No custom sharedOptions found within blueprint: ${blueprint} at ${packagePath}`;
const opts = loadSharedOptionsFromFile(`${packagePath}/cli/sharedOptions`, undefined, errorMsg);
result = _.mergeWith(result, opts, joiner);
});
return result;
}
};