diff --git a/apps/generator-cli/src/README.md b/apps/generator-cli/src/README.md index 95cf9b14162..3486db1f6cf 100644 --- a/apps/generator-cli/src/README.md +++ b/apps/generator-cli/src/README.md @@ -34,10 +34,14 @@ That version is saved in the file *openapitools.json*. Therefore, you should inc to ensure that the correct version is being used next time you call the command. If you would like to use a different version of the [OpenAPITools/openapi-generator](https://github.com/OpenAPITools/openapi-generator), -you could change it by using one of the following commands: +you can use one of the following commands: - `openapi-generator-cli version-manager list` - `openapi-generator-cli version-manager set ` +- `openapi-generator-cli version-manager resolve ` + +The `list` and `set` commands can be used to inspect or change the configured version. +The `resolve` command prints the matching version without downloading a generator or modifying `openapitools.json`. #### [added] generator config @@ -307,5 +311,3 @@ yarn add @openapitools/openapi-generator-cli@1.0.18-4.3.1 Please leave a star. - - diff --git a/apps/generator-cli/src/app/app.module.spec.ts b/apps/generator-cli/src/app/app.module.spec.ts index 3a7224deebb..df3a81b6cdd 100644 --- a/apps/generator-cli/src/app/app.module.spec.ts +++ b/apps/generator-cli/src/app/app.module.spec.ts @@ -115,6 +115,82 @@ describe('AppModule', () => { }); }); + describe('version-manager resolve is provided', () => { + beforeEach(async () => { + process.argv = ['foo', 'baz', 'version-manager', 'resolve', 'latest']; + programMock.parse.mockImplementation(() => { + expect(passThroughServiceMock.init).toHaveBeenCalledTimes(0); + }); + versionManagerServiceMock.getSelectedVersion.mockReturnValue('1.2.3'); + await fixture.onApplicationBootstrap(); + }); + + it('does not search for the latest version', () => { + expect(versionManagerServiceMock.search).toHaveBeenCalledTimes(0); + }); + + it('does not set the selected version', () => { + expect( + versionManagerServiceMock.setSelectedVersion, + ).toHaveBeenCalledTimes(0); + }); + + it('does not download', () => { + expect( + versionManagerServiceMock.downloadIfNeeded, + ).toHaveBeenCalledTimes(0); + }); + + it('parses the command', () => { + expect(programMock.parse).toHaveBeenNthCalledWith(1, process.argv); + }); + }); + + describe.each([ + ['the separate option value', ['--openapitools', 'x.json']], + ['the inline option value', ['--openapitools=x.json']], + ])('version-manager resolve with %s', (_, configArgs) => { + beforeEach(async () => { + process.argv = [ + 'foo', + 'baz', + ...configArgs, + 'version-manager', + 'resolve', + 'latest', + ]; + programMock.parse.mockImplementation(() => undefined); + versionManagerServiceMock.getSelectedVersion.mockReturnValue('1.2.3'); + await fixture.onApplicationBootstrap(); + }); + + it('does not download or initialize the pass-through service', () => { + expect( + versionManagerServiceMock.downloadIfNeeded, + ).toHaveBeenCalledTimes(0); + expect(passThroughServiceMock.init).toHaveBeenCalledTimes(0); + }); + }); + + describe('a pass-through command contains version-manager resolve as arguments', () => { + beforeEach(async () => { + process.argv = [ + 'foo', + 'baz', + 'generate', + '--some-option', + 'version-manager', + 'resolve', + ]; + versionManagerServiceMock.getSelectedVersion.mockReturnValue('1.2.3'); + await fixture.onApplicationBootstrap(); + }); + + it('initializes the pass-through service', () => { + expect(passThroughServiceMock.init).toHaveBeenCalledTimes(1); + }); + }); + describe('a custom generator is provided', () => { beforeEach(async () => { process.argv = [ diff --git a/apps/generator-cli/src/app/app.module.ts b/apps/generator-cli/src/app/app.module.ts index 50787621e92..3bdd5ee292e 100644 --- a/apps/generator-cli/src/app/app.module.ts +++ b/apps/generator-cli/src/app/app.module.ts @@ -62,12 +62,14 @@ export class AppModule implements OnApplicationBootstrap { ) {} onApplicationBootstrap = async () => { + const isVersionManagerResolveCommand = + this.isVersionManagerResolveCommand(); const hasCustomGenerator = process.argv.some( (arg) => arg === '--custom-generator' || arg.startsWith('--custom-generator='), ); - if (!hasCustomGenerator) { + if (!hasCustomGenerator && !isVersionManagerResolveCommand) { let selectedVersion = this.versionManager.getSelectedVersion(); if (!selectedVersion) { @@ -81,7 +83,35 @@ export class AppModule implements OnApplicationBootstrap { await this.versionManager.downloadIfNeeded(selectedVersion); } - await this.passThroughService.init(); + if (!isVersionManagerResolveCommand) { + await this.passThroughService.init(); + } this.program.parse(process.argv); }; + + private isVersionManagerResolveCommand() { + const args = process.argv.slice(2); + let commandIndex = 0; + + while (commandIndex < args.length) { + const arg = args[commandIndex]; + + if (arg === '--openapitools') { + commandIndex += 2; + continue; + } + + if (arg.startsWith('--openapitools=')) { + commandIndex += 1; + continue; + } + + break; + } + + return ( + args[commandIndex] === 'version-manager' && + args[commandIndex + 1] === 'resolve' + ); + } } diff --git a/apps/generator-cli/src/app/controllers/version-manager.controller.spec.ts b/apps/generator-cli/src/app/controllers/version-manager.controller.spec.ts index 9a3133c6840..5fe16a10f93 100644 --- a/apps/generator-cli/src/app/controllers/version-manager.controller.spec.ts +++ b/apps/generator-cli/src/app/controllers/version-manager.controller.spec.ts @@ -12,6 +12,7 @@ describe('VersionManagerController', () => { let commandMock: CommandMock; const log = jest.fn(); + const error = jest.fn(); const uiServiceMock = { table: jest.fn(), @@ -32,6 +33,7 @@ describe('VersionManagerController', () => { [ log, + error, ...Object.values(uiServiceMock), ...Object.values(versionManagerServiceMock), ].forEach((spy) => spy.mockReset()); @@ -42,15 +44,15 @@ describe('VersionManagerController', () => { { provide: UIService, useValue: uiServiceMock }, { provide: VersionManagerService, useValue: versionManagerServiceMock }, { provide: COMMANDER_PROGRAM, useValue: commandMock }, - { provide: LOGGER, useValue: { log } }, + { provide: LOGGER, useValue: { log, error } }, ], }).compile(); }); - it('adds 3 commands, but 2 actions', () => { - expect(commandMock.action).toHaveBeenCalledTimes(2); - expect(commandMock.command).toHaveBeenCalledTimes(3); - expect(commandMock.description).toHaveBeenCalledTimes(3); + it('adds 4 commands, but 3 actions', () => { + expect(commandMock.action).toHaveBeenCalledTimes(3); + expect(commandMock.command).toHaveBeenCalledTimes(4); + expect(commandMock.description).toHaveBeenCalledTimes(4); }); describe('commands', function () { @@ -310,5 +312,44 @@ describe('VersionManagerController', () => { ); }); }); + + describe('resolve', () => { + beforeEach(() => { + cmd = commandMock.commands['resolve ']; + }); + + afterEach(() => { + process.exitCode = 0; + }); + + it('has the correct description', () => { + expect(cmd.description).toEqual('resolve version tags to a version'); + }); + + it('prints the first matching version', async () => { + versionManagerServiceMock.search.mockReturnValue( + of([{ version: '1.2.4' }, { version: '1.2.3' }]), + ); + + await cmd.action(['latest']); + + expect(versionManagerServiceMock.search).toHaveBeenNthCalledWith(1, [ + 'latest', + ]); + expect(log).toHaveBeenNthCalledWith(1, '1.2.4'); + }); + + it('prints a message and sets a non-zero exit code if the search result is empty', async () => { + versionManagerServiceMock.search.mockReturnValue(of([])); + + await cmd.action(['latest']); + + expect(error).toHaveBeenNthCalledWith( + 1, + chalk.red('Unable to find version matching criteria "latest"'), + ); + expect(process.exitCode).toBe(1); + }); + }); }); }); diff --git a/apps/generator-cli/src/app/controllers/version-manager.controller.ts b/apps/generator-cli/src/app/controllers/version-manager.controller.ts index e5c652600b5..4137471cf22 100644 --- a/apps/generator-cli/src/app/controllers/version-manager.controller.ts +++ b/apps/generator-cli/src/app/controllers/version-manager.controller.ts @@ -22,6 +22,11 @@ export class VersionManagerController { .description('set version to use') .action((tags) => this.set(tags)); + private readonly resolveCommand = this.mainCommand + .command('resolve ') + .description('resolve version tags to a version') + .action((tags) => this.resolve(tags)); + constructor( @Inject(LOGGER) private readonly logger: LOGGER, @Inject(COMMANDER_PROGRAM) private readonly program: Command, @@ -95,6 +100,22 @@ export class VersionManagerController { ); }; + private resolve = async (versionTags: string[]) => { + const versions = await this.service.search(versionTags).toPromise(); + + if (versions.length > 0) { + this.logger.log(versions[0].version); + return; + } + + this.logger.error( + chalk.red( + `Unable to find version matching criteria "${versionTags.join(' ')}"`, + ), + ); + process.exitCode = 1; + }; + private table = (versions: Version[]) => { // Keep row order identical to the order returned in versions. const rows = versions.map((version) => { diff --git a/apps/generator-cli/src/app/services/config.service.spec.ts b/apps/generator-cli/src/app/services/config.service.spec.ts index cc37a0a4461..9947c2f4f72 100644 --- a/apps/generator-cli/src/app/services/config.service.spec.ts +++ b/apps/generator-cli/src/app/services/config.service.spec.ts @@ -56,6 +56,12 @@ describe('ConfigService', () => { it('can return a default value', () => { expect(fixture.get('unknown', 'foo')).toEqual('foo'); }); + + it('does not create or modify the config file', () => { + fixture.get('some-path'); + expect(fs.ensureFileSync).toHaveBeenCalledTimes(0); + expect(fs.writeJSONSync).toHaveBeenCalledTimes(0); + }); }); describe('the config has values', () => { @@ -69,12 +75,10 @@ describe('ConfigService', () => { }); }); - it('ensures the config file', () => { + it('does not create or modify the config file', () => { fixture.get('some-path'); - expect(fs.ensureFileSync).toHaveBeenNthCalledWith( - 1, - fixture.configFile - ); + expect(fs.ensureFileSync).toHaveBeenCalledTimes(0); + expect(fs.writeJSONSync).toHaveBeenCalledTimes(0); }); it.each([ diff --git a/apps/generator-cli/src/app/services/config.service.ts b/apps/generator-cli/src/app/services/config.service.ts index e087e00d919..e7a68f345c4 100644 --- a/apps/generator-cli/src/app/services/config.service.ts +++ b/apps/generator-cli/src/app/services/config.service.ts @@ -141,8 +141,6 @@ export class ConfigService { return result; }; - fs.ensureFileSync(this.configFile); - const fileConfig = fs.readJSONSync(this.configFile, { throws: false, encoding: 'utf8' }) ?? {}; @@ -196,6 +194,7 @@ export class ConfigService { } private write(config) { + fs.ensureFileSync(this.configFile); fs.writeJSONSync(this.configFile, config, {encoding: 'utf8', spaces: config.spaces || 2}) } }