Skip to content

Commit

Permalink
testing: add runCli helper (#27410)
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima authored Sep 26, 2024
1 parent 0cb2df9 commit a8fa515
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 147 deletions.
1 change: 0 additions & 1 deletion cli/cli.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ describe('cli', () => {
const { buildJHipster } = await import('./program.mjs');

mockCli = async (argv: string[], opts = {}) => {
// @ts-expect-error
const program = await buildJHipster({ printLogo: () => {}, ...opts, program: createProgram(), loadCommand: key => opts[`./${key}`] });
return program.parseAsync(argv);
};
Expand Down
3 changes: 2 additions & 1 deletion cli/commands.mjs → cli/commands.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* limitations under the License.
*/
import chalk from 'chalk';
import { CliCommand } from './types.js';

const removedV8 = chalk.yellow(`
Expand Down Expand Up @@ -165,6 +166,6 @@ const defaultCommands = {
workspaces: {
desc: 'Add workspaces configuration',
},
};
} as const satisfies Record<string, CliCommand>;

export default defaultCommands;
6 changes: 4 additions & 2 deletions cli/environment-builder.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const createEnvironment = (options = {}) => {
};

export default class EnvironmentBuilder {
/** @type {Environment} */
env;
devBlueprintPath;
localBlueprintPath;
localBlueprintExists;
Expand Down Expand Up @@ -276,7 +278,7 @@ export default class EnvironmentBuilder {
/**
* Get blueprints commands.
*
* @return {Object[]} blueprint commands.
* @return {Record<string, import('./types.js').CliCommand>} blueprint commands.
*/
async getBlueprintCommands() {
let blueprintsPackagePath = await this._getBlueprintPackagePaths();
Expand Down Expand Up @@ -390,7 +392,7 @@ export default class EnvironmentBuilder {
* @private
* Get blueprints commands.
*
* @return {Object[]} commands.
* @return {Record<string, import('./types.js').CliCommand>} commands.
*/
async _getBlueprintCommands(blueprintPackagePaths) {
if (!blueprintPackagePaths) {
Expand Down
2 changes: 1 addition & 1 deletion cli/jhipster-command.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default class JHipsterCommand extends Command {
/**
* Register options using generator._options structure.
* @param {object} options
* @param {string} blueprintOptionDescription - description of the blueprint that adds the option
* @param {string} [blueprintOptionDescription] - description of the blueprint that adds the option
* @return {JHipsterCommand} this;
*/
addGeneratorOptions(options, blueprintOptionDescription) {
Expand Down
150 changes: 100 additions & 50 deletions cli/program.mjs → cli/program.mts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import didYouMean from 'didyoumean';
import chalk from 'chalk';
import type Environment from 'yeoman-environment';
import type { BaseEnvironmentOptions, GeneratorMeta } from '@yeoman/types';

import { packageJson } from '../lib/index.js';
import { packageNameToNamespace } from '../generators/base/support/index.js';
import command from '../generators/base/command.js';
import baseCommand from '../generators/base/command.js';
import { GENERATOR_APP, GENERATOR_BOOTSTRAP, GENERATOR_JDL } from '../generators/generator-list.js';
import { extractArgumentsFromConfigs } from '../lib/command/index.js';
import { extractArgumentsFromConfigs, JHipsterCommandDefinition } from '../lib/command/index.js';
import { buildJDLApplicationConfig } from '../lib/command/jdl.js';
import logo from './logo.mjs';
import EnvironmentBuilder from './environment-builder.mjs';
import SUB_GENERATORS from './commands.mjs';
import JHipsterCommand from './jhipster-command.mjs';
import { CLI_NAME, done, getCommand, logger } from './utils.mjs';
import type { CliCommand } from './types.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -43,27 +46,63 @@ const JHIPSTER_NS = CLI_NAME;

const moreInfo = `\n For more info visit ${chalk.blue('https://www.jhipster.tech')}\n`;

type BuildCommands = {
program: JHipsterCommand;
commands?: Record<string, CliCommand>;
envBuilder?: EnvironmentBuilder;
env: Environment;
loadCommand?: (key: string) => Promise<(...args: any[]) => Promise<any>>;
defaultCommand?: string;
entrypointGenerator?: string;
printLogo?: () => void;
silent?: boolean;
printBlueprintLogo?: () => void;
createEnvBuilder: (options?: BaseEnvironmentOptions) => Promise<EnvironmentBuilder>;
};

type BuildJHipsterOptions = Partial<BuildCommands> & {
executableName?: string;
executableVersion?: string;
blueprints?: Record<string, string>;
lookups?: any[];
devBlueprintPath?: string;
};

type JHipsterModule = {
command?: JHipsterCommandDefinition;
default: any;
};

export const printJHipsterLogo = () => {
// eslint-disable-next-line no-console
console.log();
// eslint-disable-next-line no-console
console.log(logo);
};

const buildAllDependencies = async (generatorNames, { env, blueprintNamespaces }) => {
const buildAllDependencies = async (
generatorNames: string[],
{ env, blueprintNamespaces = [] }: { env: Environment; blueprintNamespaces?: string[] },
): Promise<Record<string, { meta: GeneratorMeta; blueprintNamespace: string }>> => {
const allDependencies = {};

const registerDependency = async ({ namespace, blueprintNamespace }) => {
const registerDependency = async ({
namespace,
blueprintNamespace,
}: {
namespace: string;
blueprintNamespace?: string;
}): Promise<JHipsterModule> => {
const meta = await env.getGeneratorMeta(namespace.includes(':') ? namespace : `${JHIPSTER_NS}:${namespace}`);
if (meta) {
allDependencies[namespace] = { meta, blueprintNamespace };
} else if (!blueprintNamespace) {
logger.warn(`Generator ${namespace} not found.`);
}
return meta?.importModule();
return (await meta?.importModule?.()) as JHipsterModule;
};

const lookupDependencyOptions = async ({ namespace, blueprintNamespace }) => {
const lookupDependencyOptions = async ({ namespace, blueprintNamespace }: { namespace: string; blueprintNamespace?: string }) => {
const lookupGeneratorAndImports = async ({ namespace, blueprintNamespace }) => {
const module = await registerDependency({ namespace, blueprintNamespace });
if (module?.command?.import) {
Expand Down Expand Up @@ -97,7 +136,11 @@ const buildAllDependencies = async (generatorNames, { env, blueprintNamespaces }
return allDependencies;
};

const addCommandGeneratorOptions = async (command, generatorMeta, { root, blueprintOptionDescription, info } = {}) => {
const addCommandGeneratorOptions = async (
command: JHipsterCommand,
generatorMeta,
{ root, blueprintOptionDescription, info }: { root?: boolean; blueprintOptionDescription?: string; info?: string } = {},
) => {
const generatorModule = await generatorMeta.importModule();
if (generatorModule.command) {
const { options, configs } = generatorModule.command;
Expand Down Expand Up @@ -138,7 +181,10 @@ const addCommandRootGeneratorOptions = async (command, generatorMeta, { usage =
}
};

export const createProgram = ({ executableName = CLI_NAME, executableVersion } = {}) => {
export const createProgram = ({
executableName = CLI_NAME,
executableVersion,
}: { executableName?: string; executableVersion?: string } = {}) => {
return (
new JHipsterCommand()
.name(executableName)
Expand All @@ -160,8 +206,9 @@ export const createProgram = ({ executableName = CLI_NAME, executableVersion } =
.option('--install-path', 'Show jhipster install path', false)
.option('--skip-regenerate', "Don't regenerate identical files", false)
.option('--skip-yo-resolve', 'Ignore .yo-resolve files', false)
.addJHipsterOptions(command.options)
.addJHipsterConfigs(command.configs)
.addJHipsterOptions(baseCommand.options)
// @ts-expect-error configs is not defined, but can be added later
.addJHipsterConfigs(baseCommand.configs)
);
};

Expand All @@ -186,37 +233,47 @@ const rejectExtraArgs = ({ program, command, extraArgs }) => {
logger.fatal(message);
};

export const buildCommands = async ({
export const buildCommands = ({
program,
commands = {},
envBuilder,
env,
loadCommand,
loadCommand = async key => {
const { default: command } = await import(`./${key}.mjs`);
return command;
},
defaultCommand = GENERATOR_APP,
entrypointGenerator,
printLogo = printJHipsterLogo,
printBlueprintLogo = () => {},
createEnvBuilder,
}) => {
silent,
}: BuildCommands) => {
/* create commands */
Object.entries(commands).forEach(([cmdName, opts]) => {
const { desc, blueprint, argument, options: commandOptions, alias, help: commandHelp, cliOnly, removed, useOptions = {} } = opts;
program
.command(cmdName, '', { isDefault: cmdName === defaultCommand, hidden: Boolean(removed) })
.description(desc + (blueprint ? chalk.yellow(` (blueprint: ${blueprint})`) : ''))
.addCommandArguments(argument)
.addCommandArguments(argument!)
.addCommandOptions(commandOptions)
.addHelpText('after', commandHelp)
.addAlias(alias)
.excessArgumentsCallback(function (receivedArgs) {
.addHelpText('after', commandHelp!)
.addAlias(alias!)
.excessArgumentsCallback(function (this, receivedArgs) {
rejectExtraArgs({ program, command: this, extraArgs: receivedArgs });
})
.lazyBuildCommand(async function (operands) {
.lazyBuildCommand(async function (this, operands) {
logger.debug(`cmd: lazyBuildCommand ${cmdName} ${operands}`);
if (removed) {
logger.fatal(removed);
return;
}

if (!silent) {
printLogo();
printBlueprintLogo();
}

const command = this;

if (cmdName === 'run') {
Expand All @@ -225,7 +282,7 @@ export const buildCommands = async ({
command.generatorNamespaces = operands.map(
namespace => `${namespace.startsWith(JHIPSTER_NS) ? '' : `${JHIPSTER_NS}-`}${namespace}`,
);
await envBuilder.lookupGenerators(command.generatorNamespaces.map(namespace => `generator-${namespace.split(':')[0]}`));
await envBuilder?.lookupGenerators(command.generatorNamespaces.map(namespace => `generator-${namespace.split(':')[0]}`));
await Promise.all(
command.generatorNamespaces.map(async namespace => {
const generatorMeta = env.getGeneratorMeta(namespace.includes(':') ? namespace : `${JHIPSTER_NS}:${namespace}`);
Expand Down Expand Up @@ -257,7 +314,7 @@ export const buildCommands = async ({
}
const allDependencies = await buildAllDependencies(boostrapGen, {
env,
blueprintNamespaces: envBuilder.getBlueprintsNamespaces(),
blueprintNamespaces: envBuilder?.getBlueprintsNamespaces(),
});
for (const [metaName, { meta: generatorMeta, blueprintNamespace }] of Object.entries(allDependencies)) {
if (blueprintNamespace) {
Expand Down Expand Up @@ -287,7 +344,7 @@ export const buildCommands = async ({
...useOptions,
commandName: cmdName,
entrypointGenerator,
blueprints: envBuilder.getBlueprintsOption(),
blueprints: envBuilder?.getBlueprintsOption(),
positionalArguments: args,
jdlDefinition,
commandsConfigs,
Expand All @@ -298,9 +355,6 @@ export const buildCommands = async ({
return Promise.resolve();
}

printLogo();
printBlueprintLogo();

if (cliOnly) {
logger.debug('Executing CLI only script');
const cliOnlyCommand = await loadCommand(cmdName);
Expand All @@ -311,17 +365,21 @@ export const buildCommands = async ({

if (cmdName === 'run') {
return Promise.all(command.generatorNamespaces.map(generator => env.run(generator, options))).then(
results => done(results.find(result => result)),
errors => done(errors.find(error => error)),
results => silent || done(results.find(result => result)),
errors => silent || done(errors.find(error => error)),
);
}
if (cmdName === 'upgrade') {
options.programName = program.name();
options.createEnvBuilder = createEnvBuilder;
}
const namespace = blueprint ? `${packageNameToNamespace(blueprint)}:${cmdName}` : `${JHIPSTER_NS}:${cmdName}`;
const generatorCommand = getCommand(namespace, args, opts);
return env.run(generatorCommand, options).then(done, done);
const generatorCommand = getCommand(namespace, args);
const promise = env.run(generatorCommand as any, options);
if (silent) {
return promise;
}
return promise.then(done, done);
});
});
};
Expand All @@ -335,43 +393,35 @@ export const buildJHipster = async ({
createEnvBuilder,
envBuilder,
commands,
printLogo,
printBlueprintLogo,
devBlueprintPath,
env,

loadCommand = async key => {
const { default: command } = await import(`./${key}.mjs`);
return command;
},
defaultCommand,
entrypointGenerator,
} = {}) => {
...buildOptions
}: BuildJHipsterOptions = {}) => {
createEnvBuilder =
createEnvBuilder ?? (async options => EnvironmentBuilder.create(options).prepare({ blueprints, lookups, devBlueprintPath }));
envBuilder = envBuilder ?? (await createEnvBuilder());
env = env ?? envBuilder.getEnvironment();
commands = commands ?? { ...SUB_GENERATORS, ...(await envBuilder.getBlueprintCommands()) };
if (!env) {
envBuilder = envBuilder ?? (await createEnvBuilder());
env = env ?? envBuilder.getEnvironment();
commands = { ...SUB_GENERATORS, ...(await envBuilder.getBlueprintCommands()), ...commands };
} else {
commands = { ...SUB_GENERATORS, ...commands };
}

await buildCommands({
buildCommands({
...buildOptions,
program,
commands,
envBuilder,
env,
loadCommand,
defaultCommand,
entrypointGenerator,
printLogo,
printBlueprintLogo,
createEnvBuilder,
});

return program;
};

export const runJHipster = async (args = {}) => {
const { argv = process.argv } = args;
const jhipsterProgram = await buildJHipster(args);
export const runJHipster = async (args: { argv?: string[] } & BuildJHipsterOptions = {}) => {
const { argv = process.argv, ...buildJHipsterOptions } = args;
const jhipsterProgram = await buildJHipster(buildJHipsterOptions);
return jhipsterProgram.parseAsync(argv);
};

Expand Down
Loading

0 comments on commit a8fa515

Please sign in to comment.