gen-jhipster
Version:
VHipster - Spring Boot + Angular/React/Vue in one handy generator
482 lines (481 loc) • 22.1 kB
JavaScript
import assert from 'node:assert';
import { randomInt } from 'node:crypto';
import { isAbsolute, join } from 'node:path';
import { mock } from 'node:test';
import { merge, snakeCase } from 'lodash-es';
import { RunContext, YeomanTest, 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 { createJHipsterLogger, lookupGeneratorsWithNamespace, normalizePathEnd } from "../utils/index.js";
const runResult = result;
const coreRunResult = result;
export const resultWithGenerator = () => runResult;
export { coreRunResult, runResult, runResult as result };
const DEFAULT_TEST_SETTINGS = { forwardCwd: true };
const DEFAULT_TEST_OPTIONS = { skipInstall: true };
const DEFAULT_TEST_ENV_OPTIONS = { skipInstall: true, dryRun: false };
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])),
};
};
const commonTestOptions = {
reproducible: true,
skipChecks: true,
reproducibleTests: true,
noInsight: true,
useVersionPlaceholders: true,
fakeKeytool: true,
skipGit: true,
skipEslint: true,
skipForks: true,
};
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 ??= { nodeDependencies: {}, customizeTemplatePaths: [] };
merge(this.sharedApplication, sharedApplication);
return this.withContextData(CONTEXT_DATA_APPLICATION_KEY, this.sharedApplication);
}
withMockedNodeDependencies() {
return this.withSharedApplication({
nodeDependencies: new Proxy({}, { get: (_target, prop) => `${snakeCase(prop.toString()).toUpperCase()}_VERSION` }),
});
}
/**
* 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.split(':').pop() });
}
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));
return runResult;
}
withTask(priorityName, method) {
return this.onGenerator(async (gen) => {
const generator = gen;
generator.on('queueOwnTasks', () => {
const priority = generator._queues[priorityName];
const queueName = priority.queueName ?? priority.priorityName;
generator.queueTask({
taskName: `test-task${randomInt(1000)}`,
queueName,
method,
});
});
});
}
}
class JHipsterTest extends YeomanTest {
constructor() {
super();
this.adapterOptions = { log: createJHipsterLogger() };
}
// @ts-expect-error testing types should be improved
run(GeneratorOrNamespace, settings, envOptions) {
return super
.run(GeneratorOrNamespace, settings, envOptions)
.withOptions({
jdlDefinition: getDefaultJDLApplicationConfig(),
})
.withAdapterOptions({ log: createJHipsterLogger() });
}
runJHipster(jhipsterGenerator, settings, envOptions) {
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' }, {
sharedOptions: { ...commonTestOptions },
});
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: 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) {
options.generatorLookupOptions ??= {};
// Default to lookup for source generators only inside tests.
options.generatorLookupOptions.lookups ??= generatorsLookup;
return EnvironmentBuilder.create(options).getEnvironment();
}
}
export function createTestHelpers(options = {}) {
const { environmentOptions = {} } = options;
const sharedOptions = {
...DEFAULT_TEST_OPTIONS,
...environmentOptions.sharedOptions,
};
const helper = new JHipsterTest();
helper.settings = { ...DEFAULT_TEST_SETTINGS, ...options.settings };
helper.environmentOptions = { ...DEFAULT_TEST_ENV_OPTIONS, ...environmentOptions, sharedOptions };
helper.generatorOptions = { ...DEFAULT_TEST_OPTIONS, ...options.generatorOptions };
// @ts-expect-error testing types should be improved
helper.getRunContextType = () => JHipsterRunContext;
return helper;
}
export const basicHelpers = createTestHelpers({ generatorOptions: { ...commonTestOptions } });
export const defaultHelpers = createTestHelpers({
generatorOptions: { skipPrettier: true, ...commonTestOptions },
environmentOptions: { dryRun: true },
});
export const skipPrettierHelpers = createTestHelpers({ generatorOptions: { skipPrettier: true, ...commonTestOptions } });
export const dryRunHelpers = createTestHelpers({
generatorOptions: { ...commonTestOptions },
environmentOptions: { dryRun: true },
});