From 3985ebf7ed8c59fd2926abc18cae92b958b89dd5 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Wed, 25 Sep 2024 23:01:51 -0300 Subject: [PATCH 1/6] testing: deprecate defineDefaults options --- lib/testing/helpers.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/testing/helpers.ts b/lib/testing/helpers.ts index 72912c20c16a..699dc9c9a15e 100644 --- a/lib/testing/helpers.ts +++ b/lib/testing/helpers.ts @@ -60,10 +60,15 @@ const defaultSharedApplication = Object.fromEntries(['CLIENT_WEBPACK_DIR'].map(k let defaultMockFactory: (original?: any) => any; let defaultAccumulateMockArgs: (mocks: Record) => Record; -export const defineDefaults = async ({ - mockFactory, - accumulateMockArgs, -}: { mockFactory?: any; accumulateMockArgs?: (mock: Record) => Record } = {}) => { +export const defineDefaults = async ( + defaults: { + /** @deprecated mock from `node:test` is used internally */ + mockFactory?: any; + /** @deprecated mock from `node:test` is used internally */ + accumulateMockArgs?: (mock: Record) => Record; + } = {}, +) => { + const { mockFactory, accumulateMockArgs } = defaults; if (mockFactory) { defaultMockFactory = mockFactory; } else if (!defaultMockFactory) { From 1f66c24b6df750e9a82fcb1ee0793827c24c1fc6 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Tue, 24 Sep 2024 20:11:15 -0300 Subject: [PATCH 2/6] cli: rework program spec --- cli/program.spec.mts | 75 +++++++++++--------------------------------- 1 file changed, 19 insertions(+), 56 deletions(-) diff --git a/cli/program.spec.mts b/cli/program.spec.mts index ca1debf43cb2..06a835380865 100644 --- a/cli/program.spec.mts +++ b/cli/program.spec.mts @@ -1,7 +1,6 @@ /* eslint-disable no-unused-expressions, no-console */ -import { expect } from 'chai'; -import { describe, it, beforeEach } from 'esmocha'; +import { describe, expect, it, beforeEach } from 'esmocha'; import { defaultHelpers as helpers } from '../lib/testing/index.js'; import { createProgram } from './program.mjs'; @@ -12,68 +11,32 @@ describe('cli - program', () => { }); describe('adding a negative option', () => { - it('when executing should not set insight', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster']) - .then(command => { - expect(command.opts().insight).to.be.undefined; - }); + it('when executing should not set insight', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster']); + expect(command.opts().insight).toBeUndefined(); }); - it('when executing with --insight should set insight to true', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster', '--insight']) - .then(command => { - expect(command.opts().insight).to.be.true; - }); + it('when executing with --insight should set insight to true', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster', '--insight']); + expect(command.opts().insight).toBe(true); }); - it('when executing with --no-insight should set insight to true', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster', '--no-insight']) - .then(command => { - expect(command.opts().insight).to.be.false; - }); + it('when executing with --no-insight should set insight to true', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster', '--no-insight']); + expect(command.opts().insight).toBe(false); }); }); describe('adding a option with default value', () => { - it('when executing should not set insight', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster']) - .then(command => { - expect(command.opts().skipYoResolve).to.be.false; - }); + it('when executing should not set insight', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster']); + expect(command.opts().skipYoResolve).toBe(false); }); - it('when executing with --skip-yo-resolve should set insight to true', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster', '--skip-yo-resolve']) - .then(command => { - expect(command.opts().skipYoResolve).to.be.true; - }); + it('when executing with --skip-yo-resolve should set insight to true', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster', '--skip-yo-resolve']); + expect(command.opts().skipYoResolve).toBe(true); }); - it('when executing with --no-skip-yo-resolve should set insight to false', () => { - return createProgram() - .exitOverride(error => { - throw error; - }) - .parseAsync(['jhipster', 'jhipster', '--no-skip-yo-resolve']) - .then(command => { - expect(command.opts().skipYoResolve).to.be.false; - }); + it('when executing with --no-skip-yo-resolve should set insight to false', async () => { + const command = await createProgram().parseAsync(['jhipster', 'jhipster', '--no-skip-yo-resolve']); + expect(command.opts().skipYoResolve).toBe(false); }); }); }); From bfd6216dfd2d75bc2bc1ca2ce25e5459c50156ac Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Tue, 24 Sep 2024 20:12:47 -0300 Subject: [PATCH 3/6] cli: convert program and command to typescript --- cli/cli.spec.mts | 1 - cli/{commands.mjs => commands.mts} | 3 +- cli/environment-builder.mjs | 6 +- cli/jhipster-command.mjs | 2 +- cli/{program.mjs => program.mts} | 106 +++++++++++++++++++++-------- cli/types.d.ts | 29 ++++++++ 6 files changed, 114 insertions(+), 33 deletions(-) rename cli/{commands.mjs => commands.mts} (98%) rename cli/{program.mjs => program.mts} (79%) create mode 100644 cli/types.d.ts diff --git a/cli/cli.spec.mts b/cli/cli.spec.mts index a1ed19599aa4..293ff7d0a57a 100644 --- a/cli/cli.spec.mts +++ b/cli/cli.spec.mts @@ -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); }; diff --git a/cli/commands.mjs b/cli/commands.mts similarity index 98% rename from cli/commands.mjs rename to cli/commands.mts index cef9b9bd3a7d..2ac329244bd3 100644 --- a/cli/commands.mjs +++ b/cli/commands.mts @@ -17,6 +17,7 @@ * limitations under the License. */ import chalk from 'chalk'; +import { CliCommand } from './types.js'; const removedV8 = chalk.yellow(` @@ -165,6 +166,6 @@ const defaultCommands = { workspaces: { desc: 'Add workspaces configuration', }, -}; +} as const satisfies Record; export default defaultCommands; diff --git a/cli/environment-builder.mjs b/cli/environment-builder.mjs index 68df6581e1ec..9ca383ec78ce 100644 --- a/cli/environment-builder.mjs +++ b/cli/environment-builder.mjs @@ -47,6 +47,8 @@ const createEnvironment = (options = {}) => { }; export default class EnvironmentBuilder { + /** @type {Environment} */ + env; devBlueprintPath; localBlueprintPath; localBlueprintExists; @@ -276,7 +278,7 @@ export default class EnvironmentBuilder { /** * Get blueprints commands. * - * @return {Object[]} blueprint commands. + * @return {Record} blueprint commands. */ async getBlueprintCommands() { let blueprintsPackagePath = await this._getBlueprintPackagePaths(); @@ -390,7 +392,7 @@ export default class EnvironmentBuilder { * @private * Get blueprints commands. * - * @return {Object[]} commands. + * @return {Record} commands. */ async _getBlueprintCommands(blueprintPackagePaths) { if (!blueprintPackagePaths) { diff --git a/cli/jhipster-command.mjs b/cli/jhipster-command.mjs index 4fb8d7cee6a3..00641e5f93a0 100644 --- a/cli/jhipster-command.mjs +++ b/cli/jhipster-command.mjs @@ -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) { diff --git a/cli/program.mjs b/cli/program.mts similarity index 79% rename from cli/program.mjs rename to cli/program.mts index 1ffbe908225f..4267500b7bc5 100644 --- a/cli/program.mjs +++ b/cli/program.mts @@ -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); @@ -43,6 +46,32 @@ 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; + envBuilder?: EnvironmentBuilder; + env: Environment; + loadCommand: (key: string) => Promise<(...args: any[]) => Promise>; + defaultCommand?: string; + entrypointGenerator?: string; + printLogo?: () => void; + printBlueprintLogo?: () => void; + createEnvBuilder: (options?: BaseEnvironmentOptions) => Promise; +}; + +type BuildJHipsterOptions = Partial & { + executableName?: string; + executableVersion?: string; + blueprints?: Record; + lookups?: any[]; + devBlueprintPath?: string; +}; + +type JHipsterModule = { + command?: JHipsterCommandDefinition; + default: any; +}; + export const printJHipsterLogo = () => { // eslint-disable-next-line no-console console.log(); @@ -50,20 +79,29 @@ export const printJHipsterLogo = () => { console.log(logo); }; -const buildAllDependencies = async (generatorNames, { env, blueprintNamespaces }) => { +const buildAllDependencies = async ( + generatorNames: string[], + { env, blueprintNamespaces = [] }: { env: Environment; blueprintNamespaces?: string[] }, +): Promise> => { const allDependencies = {}; - const registerDependency = async ({ namespace, blueprintNamespace }) => { + const registerDependency = async ({ + namespace, + blueprintNamespace, + }: { + namespace: string; + blueprintNamespace?: string; + }): Promise => { 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) { @@ -97,7 +135,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; @@ -138,7 +180,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) @@ -160,8 +205,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) ); }; @@ -197,21 +243,21 @@ export const buildCommands = async ({ printLogo = printJHipsterLogo, printBlueprintLogo = () => {}, createEnvBuilder, -}) => { +}: 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); @@ -225,7 +271,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}`); @@ -257,7 +303,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) { @@ -287,7 +333,7 @@ export const buildCommands = async ({ ...useOptions, commandName: cmdName, entrypointGenerator, - blueprints: envBuilder.getBlueprintsOption(), + blueprints: envBuilder?.getBlueprintsOption(), positionalArguments: args, jdlDefinition, commandsConfigs, @@ -320,8 +366,8 @@ export const buildCommands = async ({ 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); + return env.run(generatorCommand as any, options).then(done, done); }); }); }; @@ -346,12 +392,16 @@ export const buildJHipster = async ({ }, defaultCommand, entrypointGenerator, -} = {}) => { +}: 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({ program, @@ -369,9 +419,9 @@ export const buildJHipster = async ({ 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); }; diff --git a/cli/types.d.ts b/cli/types.d.ts new file mode 100644 index 000000000000..a6683979c639 --- /dev/null +++ b/cli/types.d.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2013-2024 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. + */ +export type CliCommand = { + desc: string; + blueprint?: string; + argument?: string[]; + options?: any[]; + alias?: string; + help?: string; + cliOnly?: boolean; + removed?: string; + useOptions?: Record; +}; From 3a04a91aab271c5343688fc77ab9183585c9fd18 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Tue, 24 Sep 2024 20:14:57 -0300 Subject: [PATCH 4/6] testing: add runCli to helpers --- cli/program.mts | 48 +++++++++++++++++++++--------------------- cli/utils.mjs | 30 +++++++++----------------- lib/testing/helpers.ts | 13 ++++++++++++ lib/utils/logger.ts | 2 +- 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/cli/program.mts b/cli/program.mts index 4267500b7bc5..2dd13813eb98 100644 --- a/cli/program.mts +++ b/cli/program.mts @@ -51,10 +51,11 @@ type BuildCommands = { commands?: Record; envBuilder?: EnvironmentBuilder; env: Environment; - loadCommand: (key: string) => Promise<(...args: any[]) => Promise>; + loadCommand?: (key: string) => Promise<(...args: any[]) => Promise>; defaultCommand?: string; entrypointGenerator?: string; printLogo?: () => void; + silent?: boolean; printBlueprintLogo?: () => void; createEnvBuilder: (options?: BaseEnvironmentOptions) => Promise; }; @@ -232,17 +233,21 @@ 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]) => { @@ -263,6 +268,12 @@ export const buildCommands = async ({ logger.fatal(removed); return; } + + if (!silent) { + printLogo(); + printBlueprintLogo(); + } + const command = this; if (cmdName === 'run') { @@ -344,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); @@ -357,8 +365,8 @@ 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') { @@ -367,7 +375,11 @@ export const buildCommands = async ({ } const namespace = blueprint ? `${packageNameToNamespace(blueprint)}:${cmdName}` : `${JHIPSTER_NS}:${cmdName}`; const generatorCommand = getCommand(namespace, args); - return env.run(generatorCommand as any, options).then(done, done); + const promise = env.run(generatorCommand as any, options); + if (silent) { + return promise; + } + return promise.then(done, done); }); }); }; @@ -381,17 +393,9 @@ 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 })); @@ -403,16 +407,12 @@ export const buildJHipster = async ({ commands = { ...SUB_GENERATORS, ...commands }; } - await buildCommands({ + buildCommands({ + ...buildOptions, program, commands, envBuilder, env, - loadCommand, - defaultCommand, - entrypointGenerator, - printLogo, - printBlueprintLogo, createEnvBuilder, }); diff --git a/cli/utils.mjs b/cli/utils.mjs index bcb9f4384893..1094f6fe16d3 100644 --- a/cli/utils.mjs +++ b/cli/utils.mjs @@ -44,31 +44,21 @@ export const getCommand = (cmd, args = []) => { return `${cmd}${cmdArgs ? ` ${cmdArgs}` : ''}`; }; -export const doneFactory = (successMsg, sponsorMsg) => { +export const doneFactory = (options = {}) => { + const { successMsg = SUCCESS_MESSAGE, sponsorMsg = SPONSOR_MESSAGE, logger: log = logger } = options; return errorOrMsg => { if (errorOrMsg instanceof Error) { - logger.error(`ERROR! ${errorOrMsg.message}`); - logger.log(errorOrMsg); + log.error(`ERROR! ${errorOrMsg.message}`); + log.log(errorOrMsg); } else if (errorOrMsg) { - logger.error(`ERROR! ${errorOrMsg}`); + log.error(`ERROR! ${errorOrMsg}`); } else if (successMsg) { - logger.log(''); - logger.log(chalk.green.bold(successMsg)); - logger.log(''); - logger.log(chalk.cyan.bold(sponsorMsg)); + log.log(''); + log.log(chalk.green.bold(successMsg)); + log.log(''); + log.log(chalk.cyan.bold(sponsorMsg)); } }; }; -export const printSuccess = () => { - if (process.exitCode === undefined || process.exitCode === 0) { - logger.log(''); - logger.log(chalk.green.bold(SUCCESS_MESSAGE)); - logger.log(''); - logger.log(chalk.cyan.bold(SPONSOR_MESSAGE)); - } else { - logger.error(`JHipster finished with code ${process.exitCode}`); - } -}; - -export const done = doneFactory(SUCCESS_MESSAGE, SPONSOR_MESSAGE); +export const done = doneFactory(); diff --git a/lib/testing/helpers.ts b/lib/testing/helpers.ts index 699dc9c9a15e..a3d28e39c097 100644 --- a/lib/testing/helpers.ts +++ b/lib/testing/helpers.ts @@ -18,6 +18,7 @@ import type CoreGenerator from '../../generators/base-core/generator.js'; import type { ApplicationConfiguration } from '../types/application/yo-rc.js'; import { getDefaultJDLApplicationConfig } from '../command/jdl.js'; import type { Entity } from '../types/base/entity.js'; +import { buildJHipster, createProgram } from '../../cli/program.mjs'; import getGenerator from './get-generator.js'; type GeneratorTestType = YeomanGenerator; @@ -404,6 +405,18 @@ class JHipsterTest extends YeomanTest { return this.run(getGenerator(jhipsterGenerator), settings, envOptions); } + runCli(command: string | string[]): JHipsterRunContext { + // Use a dummy generator which will not be used to match yeoman-test requirement. + return this.run(this.createDummyGenerator(), { namespace: 'non-used-dummy:generator' }).withEnvironmentRun(async function (this, 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 as any, silent: true }); + 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(); + }); + } + /** * Run a generator in current application context. */ diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts index 5c4df34e993c..b1b60cdd3c03 100644 --- a/lib/utils/logger.ts +++ b/lib/utils/logger.ts @@ -85,7 +85,7 @@ export const createJHipsterLogger = (options: LoggerOptions & { namespace?: stri process.exitCode = 1; }, - fatal(this: any, msg, trace) { + fatal(this: any, msg, trace?) { const fatalMessage = formatFatalMessageHeader(msg); this.console.error(...fatalMessage); if (trace) { From fe410ee60d3178e5a5ea7c17f0a34cd38350dfcb Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Tue, 24 Sep 2024 20:15:51 -0300 Subject: [PATCH 5/6] ci-cd: use runCli in test --- generators/ci-cd/generator.spec.ts | 63 +++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/generators/ci-cd/generator.spec.ts b/generators/ci-cd/generator.spec.ts index 6b35cb44b424..d897357c59cf 100644 --- a/generators/ci-cd/generator.spec.ts +++ b/generators/ci-cd/generator.spec.ts @@ -37,17 +37,10 @@ describe(`generator - ${generator}`, () => { shouldSupportFeatures(Generator); describe('blueprint support', () => testBlueprintSupport(generator)); - describe('questions', () => { - describe('without answers', () => { + describe('cli', () => { + describe('without ciCd values', () => { before(async () => { - await helpers - .runJHipster('ci-cd') - .withJHipsterConfig() - .withOptions({ - // ciCd argument is a varargs, pass an empty array to check if ciCd prompt is asked - positionalArguments: [[]], - }) - .withSkipWritingPriorities(); + await helpers.runCli('ci-cd').withJHipsterConfig().withSkipWritingPriorities(); }); it('should match order', () => { @@ -56,6 +49,56 @@ describe(`generator - ${generator}`, () => { "ciCd", "ciCdIntegrations", ] +`); + }); + }); + + describe('with invalid ciCd value', () => { + it('should exit with error', async () => { + await expect( + helpers.runCli('ci-cd foo').withJHipsterConfig().withSkipWritingPriorities(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"error: command-argument value 'foo' is invalid for argument 'ciCd'. Allowed choices are github, jenkins, gitlab, azure, travis, circle."`, + ); + }); + }); + + describe('with multiples values', () => { + before(async () => { + await helpers.runCli('ci-cd github jenkins gitlab azure').withJHipsterConfig().withSkipWritingPriorities(); + }); + + it('should match order', () => { + expect(runResult.generator.context!.ciCd).toEqual(['github', 'jenkins', 'gitlab', 'azure']); + }); + }); + + describe('with github', () => { + before(async () => { + await helpers.runCli('ci-cd github').withJHipsterConfig().withSkipWritingPriorities(); + }); + + it('should not ask ciCd question', () => { + expect(runResult.askedQuestions.map(({ name }) => name)).toMatchInlineSnapshot(` +[ + "ciCdIntegrations", +] +`); + }); + }); + + describe('with jenkins', () => { + before(async () => { + await helpers.runCli('ci-cd jenkins').withJHipsterConfig().withSkipWritingPriorities(); + }); + + it('should not ask ciCd question', () => { + expect(runResult.askedQuestions.map(({ name }) => name)).toMatchInlineSnapshot(` +[ + "ciCdIntegrations", + "insideDocker", + "sendBuildToGitlab", +] `); }); }); From 58ebd45dbcedd501429f5fd332a0ce97cb5a0a29 Mon Sep 17 00:00:00 2001 From: Marcelo Shima Date: Thu, 26 Sep 2024 07:47:00 -0300 Subject: [PATCH 6/6] Update generator.spec.ts --- generators/ci-cd/generator.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/ci-cd/generator.spec.ts b/generators/ci-cd/generator.spec.ts index d897357c59cf..52ead2bc7763 100644 --- a/generators/ci-cd/generator.spec.ts +++ b/generators/ci-cd/generator.spec.ts @@ -43,7 +43,7 @@ describe(`generator - ${generator}`, () => { await helpers.runCli('ci-cd').withJHipsterConfig().withSkipWritingPriorities(); }); - it('should match order', () => { + it('should match prompts order', () => { expect(runResult.askedQuestions.map(({ name }) => name)).toMatchInlineSnapshot(` [ "ciCd", @@ -68,7 +68,7 @@ describe(`generator - ${generator}`, () => { await helpers.runCli('ci-cd github jenkins gitlab azure').withJHipsterConfig().withSkipWritingPriorities(); }); - it('should match order', () => { + it('should populate context', () => { expect(runResult.generator.context!.ciCd).toEqual(['github', 'jenkins', 'gitlab', 'azure']); }); });