UNPKG

generator-jhipster

Version:

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

577 lines (576 loc) 26.4 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; }; import assert from 'node:assert'; import { randomInt } from 'node:crypto'; import { isAbsolute, join, relative } from 'node:path'; import { mock } from 'node:test'; import { merge, pick, snakeCase } from 'lodash-es'; import { RunContext, YeomanTest, createHelpers, result } from 'yeoman-test'; import EnvironmentBuilder, { generatorsLookup, jhipsterGeneratorsLookup } from "../../cli/environment-builder.js"; import { buildJHipster, createProgram } from "../../cli/program.js"; import BaseGenerator from "../../generators/base/index.js"; import { parseCreationTimestamp } from "../../generators/base/support/index.js"; import { CONTEXT_DATA_APPLICATION_ENTITIES_KEY } from "../../generators/base-application/support/constants.js"; import BaseCoreGenerator from "../../generators/base-core/index.js"; import { CONTEXT_DATA_APPLICATION_KEY, CONTEXT_DATA_SOURCE_KEY } from "../../generators/base-simple-application/support/constants.js"; import { JHIPSTER_CONFIG_DIR } from "../../generators/generator-constants.js"; import { getPackageRoot, isDistFolder } from "../index.js"; import { getDefaultJDLApplicationConfig } from "../jdl-config/jhipster-jdl-config.js"; import getGenerator, { getGeneratorRelativeFolder } from "../utils/get-generator.js"; import { createDelayedMutationContext, createJHipsterLogger, lookupGeneratorsWithNamespace, normalizePath, normalizePathEnd, } from "../utils/index.js"; const runResult = result; const coreRunResult = result; /** * @deprecated see typedResult */ export const resultWithGenerator = () => runResult; /** * Custom typedResult that returns a JHipsterRunResult instead of RunResult */ export const typedResult = () => runResult; export { coreRunResult, runResult, runResult as result }; const toJHipsterNamespace = (ns) => (/^jhipster[:-]/.test(ns) ? ns : `jhipster:${ns}`); const allGenerators = lookupGeneratorsWithNamespace() .map(gen => `jhipster:${gen.namespace}`) .sort(); const filterBootstrapGenerators = (gen) => !gen.startsWith('jhipster:bootstrap-') && !gen.endsWith(':bootstrap') && gen !== 'jhipster:project-name'; const composedGeneratorsToCheck = allGenerators .filter(filterBootstrapGenerators) .filter(gen => !['jhipster:bootstrap', 'jhipster:project-name'].includes(gen)); let defaultMockFactory; let defaultAccumulateMockArgs; let helpersDefaults = {}; export const resetDefaults = () => { helpersDefaults = {}; }; export const defineDefaults = async (defaults = {}) => { const { mockFactory, accumulateMockArgs, ...rest } = defaults; Object.assign(helpersDefaults, rest); if (mockFactory) { defaultMockFactory = mockFactory; } else if (!defaultMockFactory) { try { defaultMockFactory = (...args) => mock.fn(...args); } catch { throw new Error('loadMockFactory should be called before using mock'); } } if (!defaultAccumulateMockArgs) { defaultAccumulateMockArgs = accumulateMockArgs ?? ((mocks = {}) => Object.fromEntries(Object.entries(mocks) .filter(([_name, fn]) => fn.mock) .map(([name, fn]) => [ name, fn.mock.calls.map((call) => (call.arguments.length > 1 ? call.arguments : call.arguments[0])), ]))); } }; const createFiles = (workspaceFolder, configuration, entities) => { if (!configuration.baseName) { throw new Error('baseName is required'); } workspaceFolder = workspaceFolder ? normalizePathEnd(workspaceFolder) : workspaceFolder; const entityFiles = entities ? Object.fromEntries(entities?.map(entity => [`${workspaceFolder}${JHIPSTER_CONFIG_DIR}/${entity.name}.json`, entity])) : {}; configuration = { entities: entities?.map(e => e.name), ...configuration }; return { [`${workspaceFolder}.yo-rc.json`]: { 'generator-jhipster': configuration }, ...entityFiles, }; }; export const createJHipsterConfigFiles = (configuration, entities) => createFiles('', configuration, entities); export const createBlueprintFiles = (blueprintPackage, { packageJson, generator = 'test-blueprint', generatorContent, files = {} } = {}) => { generatorContent = generatorContent ?? `export const createGenerator = async env => { const BaseGenerator = await env.requireGenerator('jhipster:base'); return class extends BaseGenerator { get [BaseGenerator.INITIALIZING]() { return {}; } }; }; `; const generators = Array.isArray(generator) ? generator : [generator]; return { [`node_modules/${blueprintPackage}/package.json`]: { name: blueprintPackage, version: '9.9.9', type: 'module', ...packageJson, }, ...Object.fromEntries(generators.map(generator => [`node_modules/${blueprintPackage}/generators/${generator}/index.js`, generatorContent])), ...Object.fromEntries(Object.entries(files).map(([file, content]) => [`node_modules/${blueprintPackage}/${file}`, content])), }; }; class JHipsterRunContext extends RunContext { sharedSource; sharedApplication; workspaceApplications = []; commonWorkspacesConfig; generateApplicationsSet = false; withOptions(options) { return super.withOptions(options); } withJHipsterConfig(configuration, entities) { return this.withFiles(createFiles('', { baseName: 'jhipster', creationTimestamp: parseCreationTimestamp('2020-01-01'), ...configuration }, entities)); } withSkipWritingPriorities() { return this.withOptions({ skipPriorities: ['writing', 'postWriting', 'writingEntities', 'postWritingEntities'] }); } withWorkspacesCommonConfig(commonWorkspacesConfig) { if (this.workspaceApplications.length > 0) { throw new Error('Cannot be called after withWorkspaceApplication'); } this.commonWorkspacesConfig = { ...this.commonWorkspacesConfig, ...commonWorkspacesConfig }; return this; } withWorkspaceApplicationAtFolder(workspaceFolder, configuration, entities) { if (this.generateApplicationsSet) { throw new Error('Cannot be called after withWorkspaceApplication'); } this.workspaceApplications.push(workspaceFolder); return this.withFiles(createFiles(workspaceFolder, { ...configuration, ...this.commonWorkspacesConfig }, entities)); } withWorkspaceApplication(configuration, entities) { return this.withWorkspaceApplicationAtFolder(configuration.baseName, configuration, entities); } withWorkspacesSamples(...appNames) { return this.onBeforePrepare(async () => { try { const { default: deploymentTestSamples } = await import("../ci/support/deployment-samples.js"); for (const appName of appNames) { const application = deploymentTestSamples[appName]; if (!application) { throw new Error(`Application ${appName} not found`); } this.withWorkspaceApplicationAtFolder(appName, application); } } catch { throw new Error('Samples are currently not available to blueprint testing.'); } }); } withGenerateWorkspaceApplications(generateWorkspaces = false) { return this.onBeforePrepare(() => { this.generateApplicationsSet = true; this.withOptions({ generateApplications: true, workspacesFolders: this.workspaceApplications, workspaces: generateWorkspaces }); }); } withBlueprintConfig(config) { const { blueprint } = helpersDefaults; assert(blueprint, 'Blueprint must be configured'); return this.withYoRcConfig(blueprint, config); } /** * Use configured default blueprint. */ withConfiguredBlueprint() { const { blueprint, blueprintPackagePath, entrypointGenerator } = helpersDefaults; assert(blueprintPackagePath, 'Blueprint generators package path must be configured'); assert(blueprint, 'Blueprint must be configured'); if (entrypointGenerator) { this.withOptions({ entrypointGenerator }); } return this.withLookups([ { packagePaths: [blueprintPackagePath], }, ]).withOptions({ blueprint: [blueprint], }); } withFakeTestBlueprint(blueprintPackage, { packageJson, generator = 'test-blueprint' } = {}) { return this.withFiles(createBlueprintFiles(blueprintPackage, { packageJson, generator })) .withLookups({ localOnly: true }) .commitFiles(); } withMockedSource(options = {}) { const { except = [] } = options; this.sharedSource = new Proxy({}, { get(target, name) { if (!target[name]) { target[name] = defaultMockFactory(); } return target[name]; }, set(target, property, value) { if (except.includes(property)) { if (target[property]) { throw new Error(`Cannot set ${property} mock`); } target[property] = defaultMockFactory(value); } return true; }, }); return this.onBeforePrepare(() => defineDefaults()).withContextData(CONTEXT_DATA_SOURCE_KEY, this.sharedSource); } withSharedApplication(sharedApplication) { this.sharedApplication ??= createDelayedMutationContext({ autoDelay: true }); merge(this.sharedApplication, sharedApplication); return this.withContextData(CONTEXT_DATA_APPLICATION_KEY, this.sharedApplication); } withMockedNodeDependencies() { return this.withSharedApplication({ nodeDependencies: new Proxy({}, { get: (target, prop) => (typeof prop === 'symbol' ? target[prop] : `${snakeCase(prop).toUpperCase()}_VERSION`) }), }); } // Register importModule to have actual command for command lookups. withMockedGenerators(generators) { return super.withMockedGenerators(generators).onEnvironment(env => { for (const gen of generators.filter(gen => gen.startsWith('jhipster:'))) { const meta = env.store.getMeta(gen); meta.importModule = () => import(__rewriteRelativeImportExtension(getGenerator(gen))); } }); } /** * Mock every built-in generators except the ones in the except and bootstrap-* generators. * Note: Bootstrap generator is mocked by default. * @example * withMockedJHipsterGenerators({ except: ['jhipster:bootstrap'] }) * @example * withMockedJHipsterGenerators({ except: ['bootstrap', 'server'] }) * @example * // Mock every generator including bootstrap-* * withMockedJHipsterGenerators({ filter: () => true }) */ withMockedJHipsterGenerators(options = {}) { const optionsObj = Array.isArray(options) ? { except: options } : options; const { except = [], filter = filterBootstrapGenerators } = optionsObj; const jhipsterExceptList = new Set(except.map(toJHipsterNamespace)); return this.withMockedGenerators(allGenerators.filter(filter).filter(gen => !jhipsterExceptList.has(gen) && this.Generator !== gen)); } withJHipsterGenerators(options = {}) { const { useDefaultMocks, useMock = useDefaultMocks ? filterBootstrapGenerators : () => false } = options; const mockedGenerators = allGenerators.filter(useMock).filter(gen => this.Generator !== gen); const actualGenerators = allGenerators.filter(gen => !mockedGenerators.includes(gen)); const prefix = isDistFolder() ? 'dist/' : ''; const filePatterns = actualGenerators.map(ns => getGeneratorRelativeFolder(ns)).map(path => `${prefix}${path}/index.{j,t}s`); return this.withMockedGenerators(mockedGenerators).withLookups({ packagePaths: [getPackageRoot()], // @ts-expect-error lookups is not exported by @yeoman/types lookups: jhipsterGeneratorsLookup, filePatterns, }); } withGradleBuildTool() { return this.withFiles({ 'build.gradle': ` dependencies { // jhipster-needle-gradle-dependency } plugins { // jhipster-needle-gradle-plugins } `, }).withJHipsterConfig({ buildTool: 'gradle' }); } withContextData(key, sharedData) { this.onEnvironment(env => { const contextMap = env.getContextMap(this.targetDirectory); contextMap.set(key, sharedData); }); return this; } withJHipsterContextOptions(opts) { const { prepareEnvironment, ...otherOptions } = opts ?? {}; if (prepareEnvironment) { this.prepareEnvironment(); } return this.withJHipsterGenerators(otherOptions).withCommandName(); } withCommandName() { if (typeof this.Generator === 'string' && this.Generator.match(/^(jhipster:)?[a-zA-Z0-9:-]*$/)) { // Set the commandName to the entrypoint generator name. this.withOptions({ commandName: this.Generator.replace('jhipster:', '') }); } return this; } prepareEnvironment() { return this.onEnvironment(async (env) => { await new EnvironmentBuilder(env).prepare(); }); } async run() { const runResult = (await super.run()); if (this.sharedSource) { // Convert big objects to an identifier to avoid big snapshot and serialization issues. const cleanupArguments = (args) => args.map((arg) => { if (Array.isArray(arg)) { return cleanupArguments(arg); } const { application, relationships, entities, entity } = arg; if (application) { arg = { ...arg, application: `Application[${application.baseName}]` }; } if (entity) { arg = { ...arg, entity: `Entity[${entity.name}]` }; } for (const key of ['control', 'entities', 'source'].filter(key => arg[key])) { arg = { ...arg, [key]: `TaskParameter[${key}]` }; } if (relationships) { arg = { ...arg, relationships: relationships.map(rel => `Relationship[${rel.relationshipName}]`) }; } if (entities) { arg = { ...arg, entities: entities.map(entity => `Entity[${entity.name}]`) }; } return arg; }); runResult.sourceCallsArg = Object.fromEntries(Object.entries(defaultAccumulateMockArgs(this.sharedSource)).map(([name, args]) => [name, cleanupArguments(args)])); } runResult.composedMockedGenerators = composedGeneratorsToCheck.filter(gen => runResult.mockedGenerators[gen]?.mock.callCount() > 0); runResult.application = runResult.generator.getContextData(CONTEXT_DATA_APPLICATION_KEY, { factory: () => undefined }); const entitiesMap = runResult.generator.getContextData(CONTEXT_DATA_APPLICATION_ENTITIES_KEY, { factory: () => undefined, }); runResult.entities = entitiesMap ? Object.fromEntries(entitiesMap.entries()) : undefined; runResult.createJHipster = (gen) => runResult.create(toJHipsterNamespace(gen)); runResult.assertJHipsterConfigContent = (expected) => runResult.assertJsonFileContent('.yo-rc.json', { 'generator-jhipster': expected }); return runResult; } withTask(priorityName, method) { return this.onGenerator(async (gen) => { gen.on('queueOwnTasks', () => { const priority = gen._queues[priorityName]; const queueName = priority.queueName ?? priority.priorityName; gen.queueTask({ taskName: `test-task${randomInt(1000)}`, queueName, method, }); }); }); } } export const getCommandHelpOutput = async (command) => { const program = await buildJHipster(); const cmd = command ? program.commands.find(cmd => cmd.name() === command) : program; if (!cmd) { throw new Error(`Command ${command} not found.`); } if (command) { await cmd._lazyBuildCommandCallBack(); } return cmd.configureOutput({ getOutHelpWidth: () => 1000, getErrHelpWidth: () => 1000 }).helpInformation(); }; class JHipsterTest extends YeomanTest { commandName; // @ts-expect-error testing types should be improved run(GeneratorOrNamespace, settings, envOptions) { return super.run(GeneratorOrNamespace, settings, envOptions); } // @ts-expect-error testing types should be improved runDefault(settings, envOptions) { return super.runDefault(settings, envOptions); } async forwardsFeaturesParameter(Generator) { Generator ??= (await import(__rewriteRelativeImportExtension(getGenerator(this.defaultGenerator)))).default; const instance = new Generator([], { help: true, namespace: 'foo', resolved: 'bar', env: { cwd: 'foo' } }, { uniqueBy: 'bar' }); return instance.features.uniqueBy === 'bar'; } async getCommandHelpOutput(commandName = this.commandName) { await this.prepareTemporaryDir(); return getCommandHelpOutput(commandName); } runJHipster(jhipsterGenerator, settings, envOptions) { let jhipsterSettings; if (typeof jhipsterGenerator === 'object' || jhipsterGenerator === undefined) { jhipsterSettings = jhipsterGenerator; jhipsterGenerator = undefined; if (jhipsterGenerator === undefined) { return this.runDefault(settings, envOptions).withJHipsterContextOptions(jhipsterSettings); } } const generatorSpec = !isAbsolute(jhipsterGenerator) && !jhipsterGenerator.startsWith('@') ? toJHipsterNamespace(jhipsterGenerator) : jhipsterGenerator; const isRunJHipster = (opt) => envOptions === undefined && (opt === undefined || 'useMock' in opt || 'useDefaultMocks' in opt || 'prepareEnvironment' in opt); if (isRunJHipster(settings)) { return this.run(generatorSpec).withJHipsterContextOptions(settings); } return this.run(getGenerator(generatorSpec), settings, envOptions).withJHipsterGenerators().withCommandName(); } runCli(command, options = {}) { const { prepareEnvironment, ...buildJHipsterOptions } = options; // Use a dummy generator which will not be used to match yeoman-test requirement. const context = this.run(this.createDummyGenerator(BaseCoreGenerator), { namespace: 'non-used-dummy:generator' }); if (prepareEnvironment) { context.prepareEnvironment(); } else { // If not using EnvironmentBuilder, use the default JHipster generators lookup. context.withJHipsterGenerators(); } return context.withEnvironmentRun(async function (env) { // Customize program to throw an error instead of exiting the process on cli parse error. const program = createProgram().exitOverride(); await buildJHipster({ program, env, silent: true, ...buildJHipsterOptions }); await program.parseAsync(['jhipster', 'jhipster', ...(Array.isArray(command) ? command : command.split(' '))]); // Put the rootGenerator in context to be used in result assertions. this.generator = env.rootGenerator(); }); } runJHipsterDeployment(jhipsterGenerator, settings, envOptions) { return this.runJHipsterInApplication(jhipsterGenerator, settings, envOptions).withOptions({ destinationRoot: join(runResult.cwd, jhipsterGenerator.split(':').pop()), }); } /** * Run a generator in current application context. */ runJHipsterInApplication(jhipsterGenerator, settings, envOptions) { const context = runResult.create(getGenerator(jhipsterGenerator), settings, envOptions); return context.withJHipsterGenerators(); } runJDL(jdl, settings, envOptions) { return this.runJHipster('jdl', settings, envOptions).withOptions({ inline: jdl }); } /** * Run the JDL generator in current application context. */ runJDLInApplication(jdl, settings, envOptions) { return this.runJHipsterInApplication('jdl', settings, envOptions).withOptions({ inline: jdl }); } runTestBlueprintGenerator() { const blueprintNS = 'jhipster:test-blueprint'; class BlueprintedGenerator extends BaseGenerator { async beforeQueue() { if (!this.fromBlueprint) { await this.composeWithBlueprints(); } } rootGeneratorName() { // Force fromBlueprint to be false. return 'generator-jhipster'; } get [BaseGenerator.INITIALIZING]() { return {}; } } return this.runJHipster(blueprintNS).withGenerators([[BlueprintedGenerator, { namespace: blueprintNS }]]); } // @ts-expect-error testing types should be improved create(GeneratorOrNamespace, settings, envOptions) { return super.create(GeneratorOrNamespace, settings, envOptions); } generateDeploymentWorkspaces(commonConfig) { return this.runJHipster('workspaces') .withWorkspacesCommonConfig(commonConfig ?? {}) .withOptions({ generateWorkspaces: true, generateWith: 'docker', skipPriorities: ['prompting'], }); } async instantiateDummyBaseCoreGenerator() { return new (this.createDummyGenerator(BaseCoreGenerator))([], { namespace: 'dummy:generator', env: await this.createTestEnv(), }); } async createEnv(options) { return EnvironmentBuilder.create(options).getEnvironment(); } } const helpersPresets = { jhipster: { settings: { forwardCwd: true }, generatorOptions: { skipInstall: true, // TODO remove jdlDefinition. jdlDefinition: getDefaultJDLApplicationConfig(), }, environmentOptions: { dryRun: false, generatorLookupOptions: { // Default to lookup for source generators only inside tests. lookups: generatorsLookup, }, sharedOptions: { useVersionPlaceholders: true, reproducible: true, skipChecks: true, reproducibleTests: true, fakeKeytool: true, skipGit: true, skipEslint: true, }, }, }, default: { generatorOptions: { skipPrettier: true }, environmentOptions: { dryRun: true }, }, basic: {}, skipPrettier: { generatorOptions: { skipPrettier: true } }, dryRun: { environmentOptions: { dryRun: true } }, }; const mergeHelperOptions = (...opts) => { const ret = { adapterOptions: {}, environmentOptions: {}, generatorOptions: {}, settings: {}, }; for (const opt of opts) { ret.adapterOptions = { ...ret.adapterOptions, ...opt.adapterOptions }; ret.generatorOptions = { ...ret.generatorOptions, ...opt.generatorOptions }; ret.settings = { ...ret.settings, ...opt.settings }; ret.environmentOptions = { ...ret.environmentOptions, ...opt.environmentOptions, generatorLookupOptions: { ...ret.environmentOptions.generatorLookupOptions, ...opt.environmentOptions?.generatorLookupOptions, }, sharedOptions: { ...ret.environmentOptions?.sharedOptions, ...opt.environmentOptions?.sharedOptions, }, }; } return ret; }; export function createTestHelpers(options = {}, preset = 'default') { if (typeof options === 'string') { preset = options; options = {}; } if (options.importMeta) { const generatorDirname = options.importMeta.dirname; const relativePath = relative(getPackageRoot(), generatorDirname); if (relativePath.startsWith('generators')) { const parts = normalizePath(relativePath) .split('/') .filter(part => part && part !== 'generators'); const commandName = parts.join(':'); options.defaultGenerator ??= `jhipster:${commandName}`; options.generatorOptions ??= {}; options.generatorOptions.commandName = commandName; options.commandName = commandName; } } const mergeableOptions = pick(options, 'adapterOptions', 'environmentOptions', 'generatorOptions', 'settings'); // @ts-expect-error testing types should be improved return createHelpers({ factory: () => new JHipsterTest(), defaultGenerator: './generator.ts', getRunContextType: () => JHipsterRunContext, ...options, ...mergeHelperOptions(helpersPresets.jhipster, helpersPresets[preset], { adapterOptions: { log: createJHipsterLogger() } }, mergeableOptions), }); } export const basicHelpers = createTestHelpers('basic'); export const defaultHelpers = createTestHelpers('default'); export const skipPrettierHelpers = createTestHelpers('skipPrettier'); export const dryRunHelpers = createTestHelpers('dryRun');