Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing: add runCli helper #27410

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading