UNPKG

gen-jhipster

Version:

VHipster - Spring Boot + Angular/React/Vue in one handy generator

454 lines (453 loc) 19.2 kB
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) { if (typeof path === "string" && /^\.\.?\//.test(path)) { return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js"); }); } return path; }; /** * Copyright 2013-2026 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. */ import assert from 'node:assert'; import { existsSync } from 'node:fs'; import path, { join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { QueuedAdapter } from '@yeoman/adapter'; import chalk from 'chalk'; import { cloneDeep, mergeWith } from 'lodash-es'; import Environment from 'yeoman-environment'; import BaseGenerator from "../generators/base/index.js"; import { mergeBlueprints, parseBlueprintInfo } from "../generators/base/internal/index.js"; import { getPackageRoot, getSourceRoot, isDistFolder } from "../lib/index.js"; import { createJHipsterLogger, packageNameToNamespace } from "../lib/utils/index.js"; import { YO_RC_CONFIG_KEY, readCurrentPathYoRcFile } from "../lib/utils/yo-rc.js"; import { GENERATOR_NAME, logger } from "./utils.js"; const jhipsterDevBlueprintPath = process.env.JHIPSTER_DEV_BLUEPRINT === 'true' ? path.join(import.meta.dirname, '../.blueprint') : undefined; const devBlueprintNamespace = '@jhipster/jhipster-dev'; const localBlueprintNamespace = '@jhipster/jhipster-local'; const customizeNestedNamespace = (ns) => { if (!ns) return ns; let result = ns.replaceAll(':generators:', ':'); // Support gen-jhipster folder (e.g. when developing from a renamed clone) result = result.replace(/^gen-jhipster:/, 'jhipster:'); return result; }; // Support nested generators. export const generatorsLookup = ['generators', 'generators/*/generators']; // Local and dev blueprints generators. const localBlueprintGeneratorsLookup = ['.', './*/generators']; // Lookup for source or built generators depending on the files being used. export const jhipsterGeneratorsLookup = isDistFolder() ? generatorsLookup.map(lookup => `dist/${lookup}`) : generatorsLookup; // Lookup for source and built generators. const packagedGeneratorsLookup = generatorsLookup.flatMap(lookup => [`dist/${lookup}`, lookup]); const defaultLookupOptions = Object.freeze({ lookups: packagedGeneratorsLookup, customizeNamespace: customizeNestedNamespace, }); const createEnvironment = (options = {}) => { options.adapter = options.adapter ?? new QueuedAdapter({ log: createJHipsterLogger() }); return new Environment({ ...options, generatorLookupOptions: { ...defaultLookupOptions, ...options.generatorLookupOptions }, }); }; export default class EnvironmentBuilder { env; devBlueprintPath; localBlueprintPath; localBlueprintExists; _blueprintsWithVersion = {}; /** * Creates a new EnvironmentBuilder with a new Environment. */ static create(options = {}) { const env = createEnvironment(options); 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(); */ static async createEnv(...args) { const builder = await EnvironmentBuilder.createDefaultBuilder(...args); return builder.getEnvironment(); } /** * Creates a new EnvironmentBuilder with a new Environment and load jhipster, blueprints and sharedOptions. */ static async createDefaultBuilder(...args) { return EnvironmentBuilder.create(...args).prepare(); } static async run(args, generatorOptions = {}, envOptions = {}) { const envBuilder = await EnvironmentBuilder.createDefaultBuilder(envOptions); const env = envBuilder.getEnvironment(); await env.run(args, generatorOptions); } /** * 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; } async prepare({ blueprints, lookups, devBlueprintPath = jhipsterDevBlueprintPath, } = {}) { const devBlueprintEnabled = devBlueprintPath && existsSync(devBlueprintPath); this.env.sharedOptions.devBlueprintEnabled = devBlueprintEnabled; this.devBlueprintPath = devBlueprintEnabled ? devBlueprintPath : undefined; this.localBlueprintPath = path.join(process.cwd(), '.blueprint'); this.localBlueprintExists = this.localBlueprintPath !== this.devBlueprintPath && existsSync(this.localBlueprintPath); await this._lookupJHipster(); await this._lookupLocalBlueprint(); await this._lookupDevBlueprint(); this._loadBlueprints(blueprints); await this._lookups(lookups); await this._lookupBlueprints(); await this._loadSharedOptions(); return this; } getBlueprintsNamespaces() { return [ ...Object.keys(this._blueprintsWithVersion).map(packageName => packageNameToNamespace(packageName)), localBlueprintNamespace, ...(this.devBlueprintPath ? [devBlueprintNamespace] : []), ]; } /** * Construct blueprint option value. * * @return {String} */ getBlueprintsOption() { return Object.entries(this._blueprintsWithVersion) .map(([packageName, packageVersion]) => (packageVersion ? `${packageName}@${packageVersion}` : packageName)) .join(','); } async updateJHipsterGenerators() { await this._lookupJHipster(); await this._lookupDevBlueprint(); } /** * @private * Lookup current jhipster generators. */ async _lookupJHipster() { // Register jhipster generators. const generators = await this.env.lookup({ packagePaths: [getPackageRoot()], lookups: jhipsterGeneratorsLookup, customizeNamespace: customizeNestedNamespace, }); const generatorNamespacePrefix = 'jhipster:'; generators.forEach(generator => { // Verify jhipster generators namespace (Yeoman namespace is always jhipster:, not CLI_NAME). assert(generator.namespace.startsWith(generatorNamespacePrefix), `Error on the registered namespace ${generator.namespace}, make sure your folder is called ${GENERATOR_NAME}.`); }); // TODO: remove aliases in JHipster 10 for (const [alias, gen] of [ ['bootstrap-application', 'app:bootstrap'], ['bootstrap-application-base', 'base-application:bootstrap'], ['bootstrap-application-client', 'client:bootstrap'], ['bootstrap-application-server', 'server:bootstrap'], ['bootstrap-workspaces', 'base-workspaces:bootstrap'], ['maven', 'java-simple-application:maven'], ['gradle', 'java-simple-application:gradle'], ]) { this.env.register(class extends BaseGenerator { customLifecycle = true; async beforeQueue() { if (!this.fromBlueprint) { await this.composeWithBlueprints(); } await this.dependsOnJHipster(`jhipster:${gen}`); } }, { namespace: `jhipster:${alias}`, resolved: join(getSourceRoot(), `generators/${gen.replaceAll(':', '/generators/')}/index.${isDistFolder() ? 'j' : 't'}s`), packagePath: getPackageRoot(), }); } return this; } async _lookupLocalBlueprint() { if (this.localBlueprintExists) { // Register jhipster generators. const generators = await this.env.lookup({ packagePaths: [this.localBlueprintPath], lookups: localBlueprintGeneratorsLookup, customizeNamespace: ns => customizeNestedNamespace(ns)?.replace('.blueprint', '@jhipster/jhipster-local'), }); if (generators.length > 0) { this.env.sharedOptions.composeWithLocalBlueprint = true; } } return this; } async _lookupDevBlueprint() { if (this.devBlueprintPath) { // Register jhipster generators. await this.env.lookup({ packagePaths: [this.devBlueprintPath], lookups: localBlueprintGeneratorsLookup, customizeNamespace: ns => customizeNestedNamespace(ns)?.replace('.blueprint', '@jhipster/jhipster-dev'), }); } return this; } async _lookups(lookups = []) { for (const lookup of lookups) { await this.env.lookup(lookup); } return this; } /** * @private * Load blueprints from argv, .yo-rc.json. */ _loadBlueprints(blueprints) { this._blueprintsWithVersion = { ...this._getAllBlueprintsWithVersion(), ...blueprints, }; return this; } /** * @private * Lookup current loaded blueprints. */ async _lookupBlueprints(options = {}) { const missingBlueprints = Object.keys(this._blueprintsWithVersion).filter(blueprint => !this.env.isPackageRegistered(packageNameToNamespace(blueprint))); if (missingBlueprints && missingBlueprints.length > 0) { // Lookup for blueprints. await this.env.lookup({ ...options, filterPaths: true, packagePatterns: missingBlueprints, lookups: packagedGeneratorsLookup, }); } return this; } /** * Lookup for generators. */ async lookupGenerators(generators, options = {}) { await this.env.lookup({ filterPaths: true, ...options, packagePatterns: generators }); return this; } /** * @private * Load sharedOptions from jhipster and blueprints. */ async _loadSharedOptions() { const blueprintsPackagePath = await this._getBlueprintPackagePaths(); if (blueprintsPackagePath) { const sharedOptions = (await this._getSharedOptions(blueprintsPackagePath)) ?? {}; // Env will forward sharedOptions to every generator Object.assign(this.env.sharedOptions, sharedOptions); } return this; } /** * Get blueprints commands. */ async getBlueprintCommands() { let blueprintsPackagePath = await this._getBlueprintPackagePaths(); if (this.devBlueprintPath) { blueprintsPackagePath = blueprintsPackagePath ?? []; blueprintsPackagePath.push([devBlueprintNamespace, this.devBlueprintPath]); if (this.localBlueprintExists) { blueprintsPackagePath.push([localBlueprintNamespace, this.localBlueprintPath]); } } return this._getBlueprintCommands(blueprintsPackagePath); } /** * Get the environment. */ getEnvironment() { return this.env; } /** * Load blueprints from argv. * At this point, commander has not parsed yet because we are building it. */ _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)); } /** * Load blueprints from .yo-rc.json. */ _getBlueprintsFromYoRc() { return readCurrentPathYoRcFile()?.[YO_RC_CONFIG_KEY]?.blueprints ?? []; } /** * @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. */ async _getBlueprintPackagePaths() { const blueprints = this._blueprintsWithVersion; if (!blueprints || Object.keys(blueprints).length === 0) { return undefined; } await Promise.all(Object.keys(blueprints).map(async (blueprint) => { const namespace = packageNameToNamespace(blueprint); if (!this.env.getPackagePath(namespace)) { await this.env.lookupLocalPackages([blueprint]); } })); const blueprintsToInstall = Object.entries(blueprints) .filter(([blueprint, _version]) => { const namespace = packageNameToNamespace(blueprint); return !this.env.getPackagePath(namespace); }) .reduce((acc, [blueprint, version]) => { acc[blueprint] = version; return acc; }, {}); if (Object.keys(blueprintsToInstall).length > 0) { await 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. */ async _getBlueprintCommands(blueprintPackagePaths) { if (!blueprintPackagePaths || blueprintPackagePaths.length === 0) { return undefined; } let result = {}; for (const [blueprint, packagePath] of blueprintPackagePaths) { let blueprintCommand; const blueprintCommandFile = `${packagePath}/cli/commands`; const blueprintCommandExtension = ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts'].find(extension => existsSync(`${blueprintCommandFile}${extension}`)); if (blueprintCommandExtension) { const blueprintCommandsUrl = pathToFileURL(resolve(`${blueprintCommandFile}${blueprintCommandExtension}`)); try { blueprintCommand = (await import(__rewriteRelativeImportExtension(blueprintCommandsUrl.href))).default; const blueprintCommands = cloneDeep(blueprintCommand); Object.entries(blueprintCommands).forEach(([_command, commandSpec]) => { commandSpec.blueprint ??= blueprint; }); result = { ...result, ...blueprintCommands }; } catch { const msg = `Error parsing custom commands found within blueprint: ${blueprint} at ${blueprintCommandsUrl}`; /* eslint-disable no-console */ console.info(`${chalk.green.bold('INFO!')} ${msg}`); } } else { 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. */ async _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]; } async function loadSharedOptionsFromFile(sharedOptionsBase, msg, errorMsg) { try { const baseExtension = ['.js', '.cjs', '.mjs'].find(extension => existsSync(resolve(`${sharedOptionsBase}${extension}`))); if (baseExtension) { const { default: opts } = await import(__rewriteRelativeImportExtension(pathToFileURL(resolve(`${sharedOptionsBase}${baseExtension}`)).href)); /* 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}`, e); } } return {}; } const localPath = './.jhipster/sharedOptions'; let result = await loadSharedOptionsFromFile(localPath, `SharedOptions found at local config ${localPath}`); if (!blueprintPackagePaths) { return undefined; } for (const [blueprint, packagePath] of blueprintPackagePaths) { const errorMsg = `No custom sharedOptions found within blueprint: ${blueprint} at ${packagePath}`; const opts = await loadSharedOptionsFromFile(`${packagePath}/cli/sharedOptions`, undefined, errorMsg); result = mergeWith(result, opts, joiner); } return result; } }