From a6326a1afb76c097033364666af819431ff1d850 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Wed, 24 Apr 2019 21:53:50 +0200 Subject: [PATCH 01/22] import swagger cli as sub-generator --- .vscode/launch.json | 13 +- cli/commands.js | 3 + generators/openapi-cli/index.js | 292 ++++++++++++++++++ .../client/_ExcludeFromComponentScan.java | 11 + .../libraries/spring-cloud/apiClient.mustache | 10 + .../spring-cloud/clientConfiguration.mustache | 106 +++++++ package.json | 2 + 7 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 generators/openapi-cli/index.js create mode 100644 generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java create mode 100644 generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache create mode 100644 generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d316e1927ef..04325e311c85 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -274,6 +274,17 @@ }, "cwd": "${workspaceFolder}/test-integration/samples/app-sample-dev/", "console": "integratedTerminal" - } + }, + { + "type": "node", + "request": "launch", + "name": "jhipster openapi-cli", + "program": "${workspaceFolder}/cli/jhipster.js", + "args": [ + "openapi-cli" + ], + "cwd": "${workspaceFolder}/test-integration/samples/app-sample-dev/", + "console": "integratedTerminal" + }, ] } diff --git a/cli/commands.js b/cli/commands.js index cd8952375e36..ca56ea98b1d4 100644 --- a/cli/commands.js +++ b/cli/commands.js @@ -135,6 +135,9 @@ Example: argument: ['name'], desc: 'Create a new Spring controller' }, + 'openapi-cli': { + desc: "Jhipster Open API client generation" + }, upgrade: { desc: 'Upgrade the JHipster version, and upgrade the generated application' } diff --git a/generators/openapi-cli/index.js b/generators/openapi-cli/index.js new file mode 100644 index 000000000000..e1cbf2c401fc --- /dev/null +++ b/generators/openapi-cli/index.js @@ -0,0 +1,292 @@ +const chalk = require('chalk'); +const packagejs = require('../../package.json'); +const semver = require('semver'); +const BaseGenerator = require('../generator-base'); +const jhipsterConstants = require('../generator-constants'); +const request = require('sync-request'); +const path = require('path'); +const shelljs = require('shelljs'); +const _ = require('underscore.string'); + + +module.exports = class extends BaseGenerator { + get initializing() { + return { + init() { + this.option('regen', { + desc: 'Regenerates all saved clients', + type: Boolean, + defaults: false + }); + }, + readConfig() { + this.jhipsterAppConfig = this.getJhipsterAppConfig(); + if (!this.jhipsterAppConfig) { + this.error('Can\'t read .yo-rc.json'); + } + this.apis = this.config.get('apis') || {}; + }, + displayLogo() { + // Have Yeoman greet the user. + this.log(`\nWelcome to the ${chalk.bold.yellow('JHipster swagger-cli')} generator! ${chalk.yellow(`v${packagejs.version}\n`)}`); + }, + checkJhipster() { + const currentJhipsterVersion = this.jhipsterAppConfig.jhipsterVersion; + const minimumJhipsterVersion = packagejs.dependencies['generator-jhipster']; + if (!semver.satisfies(currentJhipsterVersion, minimumJhipsterVersion)) { + this.warning(`\nYour generated project used an old JHipster version (${currentJhipsterVersion})... you need at least (${minimumJhipsterVersion})\n`); + } + } + }; + } + + prompting() { + if (this.options.regen) { + return; + } + + const hasExistingApis = Object.keys(this.apis).length !== 0; + let actionList; + const availableDocs = []; + try { + const swaggerResources = request('GET', 'http://localhost:8080/swagger-resources', { + // This header is needed to use the custom /swagger-resources controller + // and not the default one that has only the gateway's swagger resource + headers: { Accept: 'application/json, text/javascript;' } + }); + + JSON.parse(swaggerResources.getBody()).forEach((swaggerResource) => { + availableDocs.push({ + value: { url: `http://localhost:8080${swaggerResource.location}`, name: swaggerResource.name }, + name: `${swaggerResource.name} (${swaggerResource.location})` + }); + }); + + this.log('The following swagger-docs have been found at http://localhost:8080'); + availableDocs.forEach((doc) => { + this.log(`* ${chalk.green(doc.name)} : ${doc.value.name}`); + }); + this.log(''); + + actionList = [ + { + value: 'new-detected', + name: 'Generate a new API client from one of these swagger-docs' + }, + { + value: 'new', + name: 'Generate a new API client from another swagger-doc' + } + ]; + } catch (err) { + // No live doc found on port 8080 + actionList = [ + { + value: 'new', + name: 'Generate a new API client' + } + ]; + } + + if (hasExistingApis) { + actionList.push({ value: 'all', name: 'Generate all stored API clients' }); + actionList.push({ value: 'select', name: 'Select stored API clients to generate' }); + } + + const newClient = actionList.length === 1; + + const prompts = [ + { + when: !newClient, + type: 'list', + name: 'action', + message: 'What do you want to do ?', + choices: actionList + }, + { + when: response => response.action === 'new-detected', + type: 'list', + name: 'availableDoc', + message: 'Select the doc for which you want to create a client', + choices: availableDocs + }, + { + when: response => response.action === 'new-detected' && this.jhipsterAppConfig.serviceDiscoveryType === 'eureka', + type: 'confirm', + name: 'useServiceDiscovery', + message: 'Do you want to use Eureka service discovery ?', + default: true + }, + { + when: response => response.action === 'new' || newClient, + type: 'input', + name: 'inputSpec', + message: 'Where is your Swagger/OpenAPI spec (URL or path) ?', + default: 'http://petstore.swagger.io/v2/swagger.json', + store: true + }, + { + when: response => (['new', 'new-detected'].includes(response.action) || newClient) && !response.useServiceDiscovery, + type: 'input', + name: 'cliName', + validate: (input) => { + if (!/^([a-zA-Z0-9_]*)$/.test(input)) { + return 'Your API client name cannot contain special characters or a blank space'; + } + if (input === '') { + return 'Your API client name cannot be empty'; + } + return true; + }, + message: 'What is the unique name for your API client ?', + default: 'petstore', + store: true + }, + { + when: response => ['new', 'new-detected'].includes(response.action) || newClient, + type: 'confirm', + name: 'saveConfig', + message: 'Do you want to save this config for future reuse ?', + default: false + }, + { + when: response => response.action === 'select', + type: 'checkbox', + name: 'selected', + message: 'Select which APIs you want to generate', + choices: () => { + const choices = []; + Object.keys(this.apis).forEach((cliName) => { + choices.push({ + name: `${cliName} (${this.apis[cliName].spec})`, + value: { cliName, spec: this.apis[cliName] } + }); + }); + return choices; + } + } + ]; + + const done = this.async(); + this.prompt(prompts).then((props) => { + if (props.availableDoc !== undefined) { + props.inputSpec = props.availableDoc.url; + props.cliName = props.availableDoc.name; + } + this.props = props; + done(); + }); + } + + get configuring() { + return { + determineApisToGenerate() { + this.apisToGenerate = {}; + if (this.options.regen || this.props.action === 'all') { + this.apisToGenerate = this.apis; + } else if (['new', 'new-detected'].includes(this.props.action) || this.props.action === undefined) { + this.apisToGenerate[this.props.cliName] = { spec: this.props.inputSpec, useServiceDiscovery: this.props.useServiceDiscovery }; + } else if (this.props.action === 'select') { + this.props.selected.forEach(function (selection) { + this.apisToGenerate[selection.cliName] = selection.spec; + }); + } + }, + + saveConfig() { + if (!this.options.regen && this.props.saveConfig) { + this.apis[this.props.cliName] = this.apisToGenerate[this.props.cliName]; + this.config.set('apis', this.apis); + } + } + }; + } + + get writing() { + return { + callSwaggerCodegen() { + this.baseName = this.jhipsterAppConfig.baseName; + this.authenticationType = this.jhipsterAppConfig.authenticationType; + this.packageName = this.jhipsterAppConfig.packageName; + this.packageFolder = this.jhipsterAppConfig.packageFolder; + this.buildTool = this.jhipsterAppConfig.buildTool; + + this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; + const jarPath = path.resolve(__dirname, '../jar/openapi-generator-cli-3.0.0-SNAPSHOT.jar'); + + Object.keys(this.apisToGenerate).forEach((cliName) => { + const inputSpec = this.apisToGenerate[cliName].spec; + const cliPackage = `${this.packageName}.client.${_.underscored(cliName)}`; + this.log(chalk.green(`Generating client code for ${cliName} (${inputSpec})`)); + + let execLine = `java -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java -jar ${jarPath} generate` + + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} -l spring --library spring-cloud ` + + ` -i ${inputSpec} --artifact-id ${_.camelize(cliName)} --api-package ${cliPackage}.api` + + ` --model-package ${cliPackage}.model` + + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + + ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},title=${_.camelize(cliName)}`; + if (this.apisToGenerate[cliName].useServiceDiscovery) { + execLine += ' --additional-properties ribbon=true'; + } + this.log(execLine); + shelljs.exec(execLine); + }); + }, + + writeTemplates() { + // function to use directly template + this.template = (source, destination) => this.fs.copyTpl( + this.templatePath(source), + this.destinationPath(destination), + this + ); + if (this.buildTool === 'maven') { + if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { + let exclusions; + if (this.authenticationType === 'session') { + exclusions = ' \n' + + ' \n' + + ' org.springframework.cloud\n' + + ' spring-cloud-starter-ribbon\n' + + ' \n' + + ' '; + } + this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-openfeign', null, exclusions); + } + this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-oauth2'); + } else if (this.buildTool === 'gradle') { + if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { + if (this.authenticationType === 'session') { + const content = 'compile \'org.springframework.cloud:spring-cloud-starter-openfeign\', { exclude group: \'org.springframework.cloud\', module: \'spring-cloud-starter-ribbon\' }'; + this.rewriteFile('./build.gradle', 'jhipster-needle-gradle-dependency', content); + } else { + this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-openfeign'); + } + } + this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-oauth2'); + } + + const mainClassFile = `${this.javaDir + this.getMainClassName()}.java`; + + if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { + this.rewriteFile(mainClassFile, 'import org.springframework.core.env.Environment;', 'import org.springframework.cloud.openfeign.EnableFeignClients;'); + } + this.rewriteFile(mainClassFile, 'import org.springframework.core.env.Environment;', 'import org.springframework.context.annotation.ComponentScan;'); + + const componentScan = `${'@ComponentScan( excludeFilters = {\n' + + ' @ComponentScan.Filter('}${this.packageName}.client.ExcludeFromComponentScan.class)\n` + + '})'; + this.rewriteFile(mainClassFile, '@SpringBootApplication', componentScan); + + if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { + this.rewriteFile(mainClassFile, '@SpringBootApplication', '@EnableFeignClients'); + } + this.template('src/main/java/package/client/_ExcludeFromComponentScan.java', `${this.javaDir}/client/ExcludeFromComponentScan.java`); + } + }; + } + + end() { + this.log('End of swagger-cli generator'); + } +}; diff --git a/generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java b/generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java new file mode 100644 index 000000000000..c47bdad39fa1 --- /dev/null +++ b/generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java @@ -0,0 +1,11 @@ +package <%=packageName%>.client; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ExcludeFromComponentScan { +} \ No newline at end of file diff --git a/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache b/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache new file mode 100644 index 000000000000..75e906bbefcf --- /dev/null +++ b/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache @@ -0,0 +1,10 @@ +package {{package}}; + +import org.springframework.cloud.openfeign.FeignClient; +import {{configPackage}}.ClientConfiguration; + +{{=<% %>=}} +@FeignClient(name="${<%title%>.name:<%title%>}", url="${<%title%>.url:<%^ribbon%><%basePath%><%/ribbon%>}", configuration = ClientConfiguration.class) +<%={{ }}=%> +public interface {{classname}}Client extends {{classname}} { +} \ No newline at end of file diff --git a/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache b/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache new file mode 100644 index 000000000000..4d98f276f75b --- /dev/null +++ b/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache @@ -0,0 +1,106 @@ +package {{configPackage}}; + +import {{basePackage}}.ExcludeFromComponentScan; +import feign.auth.BasicAuthRequestInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitResourceDetails; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; + +@Configuration +@ExcludeFromComponentScan +@EnableConfigurationProperties +public class ClientConfiguration { + +{{#authMethods}} + {{#isBasic}} + {{=<% %>=}} + @Value("${<%title%>.security.<%name%>.username:}") + private String <%name%>Username; + + @Value("${<%title%>.security.<%name%>.password:}") + private String <%name%>Password; + <%={{ }}=%> + + @Bean + @ConditionalOnProperty(name = "{{{title}}}.security.{{{name}}}.username") + public BasicAuthRequestInterceptor {{{name}}}RequestInterceptor() { + return new BasicAuthRequestInterceptor(this.{{{name}}}Username, this.{{{name}}}Password); + } + + {{/isBasic}} + {{#isApiKey}} + @Value("${ {{{title}}}.security.{{{name}}}.key:}") + private String {{{name}}}Key; + + @Bean + @ConditionalOnProperty(name = "{{{title}}}.security.{{{name}}}.key") + public ApiKeyRequestInterceptor {{{name}}}RequestInterceptor() { + return new ApiKeyRequestInterceptor({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{{keyParamName}}}", this.{{{name}}}Key); + } + + {{/isApiKey}} + {{#isOAuth}} + @Bean + @ConditionalOnProperty("{{{title}}}.security.{{{name}}}.client-id") + public OAuth2FeignRequestInterceptor {{{name}}}RequestInterceptor() { + return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), {{{name}}}ResourceDetails()); + } + + {{#isCode}} + @Bean + @ConditionalOnProperty("{{{title}}}.security.{{{name}}}.client-id") + @ConfigurationProperties("{{{title}}}.security.{{{name}}}") + public AuthorizationCodeResourceDetails {{{name}}}ResourceDetails() { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setAccessTokenUri("{{{tokenUrl}}}"); + details.setUserAuthorizationUri("{{{authorizationUrl}}}"); + return details; + } + + {{/isCode}} + {{#isPassword}} + @Bean + @ConditionalOnProperty("{{{title}}}.security.{{{name}}}.client-id") + @ConfigurationProperties("{{{title}}}.security.{{{name}}}") + public ResourceOwnerPasswordResourceDetails {{{name}}}ResourceDetails() { + ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); + details.setAccessTokenUri("{{{tokenUrl}}}"); + return details; + } + + {{/isPassword}} + {{#isApplication}} + @Bean + @ConditionalOnProperty("{{{title}}}.security.{{{name}}}.client-id") + @ConfigurationProperties("{{{title}}}.security.{{{name}}}") + public ClientCredentialsResourceDetails {{{name}}}ResourceDetails() { + ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); + details.setAccessTokenUri("{{{tokenUrl}}}"); + return details; + } + + {{/isApplication}} + {{#isImplicit}} + @Bean + @ConditionalOnProperty("{{{title}}}.security.{{{name}}}.client-id") + @ConfigurationProperties("{{{title}}}.security.{{{name}}}") + public ImplicitResourceDetails {{{name}}}ResourceDetails() { + ImplicitResourceDetails details = new ImplicitResourceDetails(); + details.setUserAuthorizationUri("{{{authorizationUrl}}}"); + return details; + } + + {{/isImplicit}} + {{/isOAuth}} +{{/authMethods}} +} diff --git a/package.json b/package.json index b878748e91fc..534130ee6cb0 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,8 @@ "tabtab": "2.2.2", "through2": "3.0.1", "uuid": "3.3.2", + "sync-request": "6.0.0", + "underscore.string": "3.3.4", "yeoman-environment": "2.3.4", "yeoman-generator": "3.2.0", "yo": "3.1.0" From 5ce694ab9e0cab70c8936f9a9691237cd923957d Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Wed, 24 Apr 2019 22:00:35 +0200 Subject: [PATCH 02/22] fix openapi-cli index.js to make it work as sub-generator --- generators/openapi-cli/index.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/generators/openapi-cli/index.js b/generators/openapi-cli/index.js index e1cbf2c401fc..535f848f6a6c 100644 --- a/generators/openapi-cli/index.js +++ b/generators/openapi-cli/index.js @@ -10,17 +10,21 @@ const _ = require('underscore.string'); module.exports = class extends BaseGenerator { + constructor(args, opts) { + super(args, opts); + // This adds support for a `--from-cli` flag + this.option('regen', { + desc: 'Regenerates all saved clients', + type: Boolean, + defaults: false + }); + this.registerPrettierTransform(); + } + get initializing() { return { - init() { - this.option('regen', { - desc: 'Regenerates all saved clients', - type: Boolean, - defaults: false - }); - }, - readConfig() { - this.jhipsterAppConfig = this.getJhipsterAppConfig(); + getConfig() { + this.jhipsterAppConfig = this.config; if (!this.jhipsterAppConfig) { this.error('Can\'t read .yo-rc.json'); } @@ -28,14 +32,7 @@ module.exports = class extends BaseGenerator { }, displayLogo() { // Have Yeoman greet the user. - this.log(`\nWelcome to the ${chalk.bold.yellow('JHipster swagger-cli')} generator! ${chalk.yellow(`v${packagejs.version}\n`)}`); - }, - checkJhipster() { - const currentJhipsterVersion = this.jhipsterAppConfig.jhipsterVersion; - const minimumJhipsterVersion = packagejs.dependencies['generator-jhipster']; - if (!semver.satisfies(currentJhipsterVersion, minimumJhipsterVersion)) { - this.warning(`\nYour generated project used an old JHipster version (${currentJhipsterVersion})... you need at least (${minimumJhipsterVersion})\n`); - } + this.log(`\nWelcome to the ${chalk.bold.yellow('JHipster openapi-cli')} sub-generator! ${chalk.yellow(`v${packagejs.version}\n`)}`); } }; } @@ -287,6 +284,6 @@ module.exports = class extends BaseGenerator { } end() { - this.log('End of swagger-cli generator'); + this.log('End of openapi-cli generator'); } }; From e44fa1e96aeea6c94a1c2d2640ecf3c0648794c3 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 25 Apr 2019 00:41:06 +0200 Subject: [PATCH 03/22] use node openapi-generator-cli instead of jar file See https://www.npmjs.com/package/@openapitools/openapi-generator-cli --- .../client/templates/angular/package.json.ejs | 1 + .../client/templates/react/package.json.ejs | 1 + generators/openapi-cli/index.js | 22 ++++++++----------- generators/server/templates/package.json.ejs | 3 +++ package.json | 1 + 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/generators/client/templates/angular/package.json.ejs b/generators/client/templates/angular/package.json.ejs index d0d9f1d3a000..76c5875cfbab 100644 --- a/generators/client/templates/angular/package.json.ejs +++ b/generators/client/templates/angular/package.json.ejs @@ -176,6 +176,7 @@ "build": "<%= clientPackageManager %> run webpack:prod", "test": "<%= clientPackageManager %> run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js", "test:watch": "<%= clientPackageManager %> run test <%= optionsForwarder %>--watch", + "openapi-generator": "openapi-generator", "webpack:dev": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal", "webpack:dev-verbose": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal", "webpack:build:main": "<%= clientPackageManager %> run webpack <%= optionsForwarder %>--config webpack/webpack.dev.js --env.stats=minimal", diff --git a/generators/client/templates/react/package.json.ejs b/generators/client/templates/react/package.json.ejs index b1274c5d0e4f..1f7d2c1c79af 100644 --- a/generators/client/templates/react/package.json.ejs +++ b/generators/client/templates/react/package.json.ejs @@ -197,6 +197,7 @@ limitations under the License. "test": "<%= clientPackageManager %> run lint && <%= clientPackageManager %> run jest", "test-ci": "<%= clientPackageManager %> run lint && <%= clientPackageManager %> run jest:update", "test:watch": "<%= clientPackageManager %> test <%= optionsForwarder %>--watch", + "openapi-generator": "openapi-generator", "webpack:dev": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --port=9060 --env.stats=minimal", "webpack:dev-verbose": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --port=9060 --profile --progress --env.stats=normal", "webpack:build:main": "<%= clientPackageManager %> run webpack <%= optionsForwarder %>--config webpack/webpack.dev.js --env.stats=minimal", diff --git a/generators/openapi-cli/index.js b/generators/openapi-cli/index.js index 535f848f6a6c..1746fdfa0b76 100644 --- a/generators/openapi-cli/index.js +++ b/generators/openapi-cli/index.js @@ -24,10 +24,6 @@ module.exports = class extends BaseGenerator { get initializing() { return { getConfig() { - this.jhipsterAppConfig = this.config; - if (!this.jhipsterAppConfig) { - this.error('Can\'t read .yo-rc.json'); - } this.apis = this.config.get('apis') || {}; }, displayLogo() { @@ -108,7 +104,7 @@ module.exports = class extends BaseGenerator { choices: availableDocs }, { - when: response => response.action === 'new-detected' && this.jhipsterAppConfig.serviceDiscoveryType === 'eureka', + when: response => response.action === 'new-detected' && this.config.get('serviceDiscoveryType') === 'eureka', type: 'confirm', name: 'useServiceDiscovery', message: 'Do you want to use Eureka service discovery ?', @@ -202,22 +198,22 @@ module.exports = class extends BaseGenerator { get writing() { return { callSwaggerCodegen() { - this.baseName = this.jhipsterAppConfig.baseName; - this.authenticationType = this.jhipsterAppConfig.authenticationType; - this.packageName = this.jhipsterAppConfig.packageName; - this.packageFolder = this.jhipsterAppConfig.packageFolder; - this.buildTool = this.jhipsterAppConfig.buildTool; + this.baseName = this.config.get('baseName'); + this.authenticationType = this.config.get('authenticationType'); + this.packageName = this.config.get('packageName'); + this.clientPackageManager = this.config.get('clientPackageManager'); + this.packageFolder = this.config.get('packageFolder'); + this.buildTool = this.config.get('buildTool'); this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; - const jarPath = path.resolve(__dirname, '../jar/openapi-generator-cli-3.0.0-SNAPSHOT.jar'); Object.keys(this.apisToGenerate).forEach((cliName) => { const inputSpec = this.apisToGenerate[cliName].spec; const cliPackage = `${this.packageName}.client.${_.underscored(cliName)}`; this.log(chalk.green(`Generating client code for ${cliName} (${inputSpec})`)); - let execLine = `java -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java -jar ${jarPath} generate` + - ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} -l spring --library spring-cloud ` + + let execLine = `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ` + + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + ` -i ${inputSpec} --artifact-id ${_.camelize(cliName)} --api-package ${cliPackage}.api` + ` --model-package ${cliPackage}.model` + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + diff --git a/generators/server/templates/package.json.ejs b/generators/server/templates/package.json.ejs index 94c656a8a48b..e3969b90eb53 100644 --- a/generators/server/templates/package.json.ejs +++ b/generators/server/templates/package.json.ejs @@ -31,6 +31,9 @@ <%_ }); _%> "generator-jhipster": "<%= jhipsterVersion %>" }, + scripts: { + "openapi-generator": "openapi-generator" + }, "engines": { "node": ">=8.9.0" } diff --git a/package.json b/package.json index 534130ee6cb0..feca438472c2 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "uuid": "3.3.2", "sync-request": "6.0.0", "underscore.string": "3.3.4", + "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", "yeoman-environment": "2.3.4", "yeoman-generator": "3.2.0", "yo": "3.1.0" From d14e955487fffb6e9927c7ee5a422bb360f24949 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Fri, 26 Apr 2019 15:06:21 +0200 Subject: [PATCH 04/22] fix iteration over apis to regenerate --- generators/openapi-cli/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generators/openapi-cli/index.js b/generators/openapi-cli/index.js index 1746fdfa0b76..ae0adc1ecb75 100644 --- a/generators/openapi-cli/index.js +++ b/generators/openapi-cli/index.js @@ -180,7 +180,7 @@ module.exports = class extends BaseGenerator { } else if (['new', 'new-detected'].includes(this.props.action) || this.props.action === undefined) { this.apisToGenerate[this.props.cliName] = { spec: this.props.inputSpec, useServiceDiscovery: this.props.useServiceDiscovery }; } else if (this.props.action === 'select') { - this.props.selected.forEach(function (selection) { + this.props.selected.forEach((selection) => { this.apisToGenerate[selection.cliName] = selection.spec; }); } From e04e5f521d207ac570e3c9cdbcd0703dec598d95 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Fri, 26 Apr 2019 15:10:26 +0200 Subject: [PATCH 05/22] add openapi-cli command description and USAGE file --- cli/commands.js | 2 +- generators/openapi-cli/USAGE | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 generators/openapi-cli/USAGE diff --git a/cli/commands.js b/cli/commands.js index ca56ea98b1d4..af9532686ddc 100644 --- a/cli/commands.js +++ b/cli/commands.js @@ -136,7 +136,7 @@ Example: desc: 'Create a new Spring controller' }, 'openapi-cli': { - desc: "Jhipster Open API client generation" + desc: "Generates client code from an OpenAPI/Swagger definition" }, upgrade: { desc: 'Upgrade the JHipster version, and upgrade the generated application' diff --git a/generators/openapi-cli/USAGE b/generators/openapi-cli/USAGE new file mode 100644 index 000000000000..a2ea8630df37 --- /dev/null +++ b/generators/openapi-cli/USAGE @@ -0,0 +1,5 @@ +Description: + Generates client code from an OpenAPI/Swagger definition + +Example: + jhipster openapi-cli From e6cfa45e5e727ae01887bbd076e9610196b3b409 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 2 May 2019 15:41:24 +0200 Subject: [PATCH 06/22] create promts.js and add generatorName property --- generators/openapi-cli/index.js | 273 ++++++++++-------------------- generators/openapi-cli/prompts.js | 254 +++++++++++++++++++++++++++ 2 files changed, 342 insertions(+), 185 deletions(-) create mode 100644 generators/openapi-cli/prompts.js diff --git a/generators/openapi-cli/index.js b/generators/openapi-cli/index.js index ae0adc1ecb75..329e138590ea 100644 --- a/generators/openapi-cli/index.js +++ b/generators/openapi-cli/index.js @@ -1,13 +1,28 @@ -const chalk = require('chalk'); -const packagejs = require('../../package.json'); -const semver = require('semver'); -const BaseGenerator = require('../generator-base'); -const jhipsterConstants = require('../generator-constants'); -const request = require('sync-request'); +/** + * Copyright 2013-2019 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 + * + * http://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. + */ const path = require('path'); const shelljs = require('shelljs'); const _ = require('underscore.string'); - +const chalk = require('chalk'); +const BaseGenerator = require('../generator-base'); +const jhipsterConstants = require('../generator-constants'); +const prompts = require('./prompts'); module.exports = class extends BaseGenerator { constructor(args, opts) { @@ -24,172 +39,46 @@ module.exports = class extends BaseGenerator { get initializing() { return { getConfig() { - this.apis = this.config.get('apis') || {}; + this.openApiClients = this.config.get('openApiClients') || {}; }, displayLogo() { // Have Yeoman greet the user. - this.log(`\nWelcome to the ${chalk.bold.yellow('JHipster openapi-cli')} sub-generator! ${chalk.yellow(`v${packagejs.version}\n`)}`); + this.log(chalk.white('Welcome to the JHipster OpenApi client Sub-Generator')); } }; } - prompting() { - if (this.options.regen) { - return; - } - - const hasExistingApis = Object.keys(this.apis).length !== 0; - let actionList; - const availableDocs = []; - try { - const swaggerResources = request('GET', 'http://localhost:8080/swagger-resources', { - // This header is needed to use the custom /swagger-resources controller - // and not the default one that has only the gateway's swagger resource - headers: { Accept: 'application/json, text/javascript;' } - }); - - JSON.parse(swaggerResources.getBody()).forEach((swaggerResource) => { - availableDocs.push({ - value: { url: `http://localhost:8080${swaggerResource.location}`, name: swaggerResource.name }, - name: `${swaggerResource.name} (${swaggerResource.location})` - }); - }); - - this.log('The following swagger-docs have been found at http://localhost:8080'); - availableDocs.forEach((doc) => { - this.log(`* ${chalk.green(doc.name)} : ${doc.value.name}`); - }); - this.log(''); - - actionList = [ - { - value: 'new-detected', - name: 'Generate a new API client from one of these swagger-docs' - }, - { - value: 'new', - name: 'Generate a new API client from another swagger-doc' - } - ]; - } catch (err) { - // No live doc found on port 8080 - actionList = [ - { - value: 'new', - name: 'Generate a new API client' - } - ]; - } - - if (hasExistingApis) { - actionList.push({ value: 'all', name: 'Generate all stored API clients' }); - actionList.push({ value: 'select', name: 'Select stored API clients to generate' }); - } - - const newClient = actionList.length === 1; - - const prompts = [ - { - when: !newClient, - type: 'list', - name: 'action', - message: 'What do you want to do ?', - choices: actionList - }, - { - when: response => response.action === 'new-detected', - type: 'list', - name: 'availableDoc', - message: 'Select the doc for which you want to create a client', - choices: availableDocs - }, - { - when: response => response.action === 'new-detected' && this.config.get('serviceDiscoveryType') === 'eureka', - type: 'confirm', - name: 'useServiceDiscovery', - message: 'Do you want to use Eureka service discovery ?', - default: true - }, - { - when: response => response.action === 'new' || newClient, - type: 'input', - name: 'inputSpec', - message: 'Where is your Swagger/OpenAPI spec (URL or path) ?', - default: 'http://petstore.swagger.io/v2/swagger.json', - store: true - }, - { - when: response => (['new', 'new-detected'].includes(response.action) || newClient) && !response.useServiceDiscovery, - type: 'input', - name: 'cliName', - validate: (input) => { - if (!/^([a-zA-Z0-9_]*)$/.test(input)) { - return 'Your API client name cannot contain special characters or a blank space'; - } - if (input === '') { - return 'Your API client name cannot be empty'; - } - return true; - }, - message: 'What is the unique name for your API client ?', - default: 'petstore', - store: true - }, - { - when: response => ['new', 'new-detected'].includes(response.action) || newClient, - type: 'confirm', - name: 'saveConfig', - message: 'Do you want to save this config for future reuse ?', - default: false - }, - { - when: response => response.action === 'select', - type: 'checkbox', - name: 'selected', - message: 'Select which APIs you want to generate', - choices: () => { - const choices = []; - Object.keys(this.apis).forEach((cliName) => { - choices.push({ - name: `${cliName} (${this.apis[cliName].spec})`, - value: { cliName, spec: this.apis[cliName] } - }); - }); - return choices; - } - } - ]; - - const done = this.async(); - this.prompt(prompts).then((props) => { - if (props.availableDoc !== undefined) { - props.inputSpec = props.availableDoc.url; - props.cliName = props.availableDoc.name; - } - this.props = props; - done(); - }); + get prompting() { + return { + askActionType: prompts.askActionType, + askExistingAvailableDocs: prompts.askExistingAvailableDocs, + askGenerationInfos: prompts.askGenerationInfos + }; } get configuring() { return { determineApisToGenerate() { - this.apisToGenerate = {}; + this.clientsToGenerate = {}; if (this.options.regen || this.props.action === 'all') { - this.apisToGenerate = this.apis; - } else if (['new', 'new-detected'].includes(this.props.action) || this.props.action === undefined) { - this.apisToGenerate[this.props.cliName] = { spec: this.props.inputSpec, useServiceDiscovery: this.props.useServiceDiscovery }; + this.clientsToGenerate = this.openApiClients; + } else if (this.props.action === 'new' || this.props.action === undefined) { + this.clientsToGenerate[this.props.cliName] = { + spec: this.props.inputSpec, + useServiceDiscovery: this.props.useServiceDiscovery, + generatorName: this.props.generatorName + }; } else if (this.props.action === 'select') { - this.props.selected.forEach((selection) => { - this.apisToGenerate[selection.cliName] = selection.spec; + this.props.selected.forEach(selection => { + this.clientsToGenerate[selection.cliName] = selection.spec; }); } }, saveConfig() { if (!this.options.regen && this.props.saveConfig) { - this.apis[this.props.cliName] = this.apisToGenerate[this.props.cliName]; - this.config.set('apis', this.apis); + this.openApiClients[this.props.cliName] = this.clientsToGenerate[this.props.cliName]; + this.config.set('openApiClients', this.openApiClients); } } }; @@ -197,7 +86,7 @@ module.exports = class extends BaseGenerator { get writing() { return { - callSwaggerCodegen() { + callOpenApiGenerator() { this.baseName = this.config.get('baseName'); this.authenticationType = this.config.get('authenticationType'); this.packageName = this.config.get('packageName'); @@ -207,19 +96,25 @@ module.exports = class extends BaseGenerator { this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; - Object.keys(this.apisToGenerate).forEach((cliName) => { - const inputSpec = this.apisToGenerate[cliName].spec; - const cliPackage = `${this.packageName}.client.${_.underscored(cliName)}`; - this.log(chalk.green(`Generating client code for ${cliName} (${inputSpec})`)); - - let execLine = `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ` + - ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + - ` -i ${inputSpec} --artifact-id ${_.camelize(cliName)} --api-package ${cliPackage}.api` + - ` --model-package ${cliPackage}.model` + - ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + - ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},title=${_.camelize(cliName)}`; - if (this.apisToGenerate[cliName].useServiceDiscovery) { - execLine += ' --additional-properties ribbon=true'; + Object.keys(this.clientsToGenerate).forEach(cliName => { + const inputSpec = this.clientsToGenerate[cliName].spec; + const generatorName = this.clientsToGenerate[cliName].generatorName; + let execLine; + if (generatorName === 'spring') { + const cliPackage = `${this.packageName}.client.${_.underscored(cliName)}`; + this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); + + execLine = + `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis ` + + '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ' + + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + + ` -i ${inputSpec} --artifact-id ${_.camelize(cliName)} --api-package ${cliPackage}.api` + + ` --model-package ${cliPackage}.model` + + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + + ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},title=${_.camelize(cliName)}`; + if (this.clientsToGenerate[cliName].useServiceDiscovery) { + execLine += ' --additional-properties ribbon=true'; + } } this.log(execLine); shelljs.exec(execLine); @@ -227,22 +122,17 @@ module.exports = class extends BaseGenerator { }, writeTemplates() { - // function to use directly template - this.template = (source, destination) => this.fs.copyTpl( - this.templatePath(source), - this.destinationPath(destination), - this - ); if (this.buildTool === 'maven') { if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { let exclusions; if (this.authenticationType === 'session') { - exclusions = ' \n' + - ' \n' + - ' org.springframework.cloud\n' + - ' spring-cloud-starter-ribbon\n' + - ' \n' + - ' '; + exclusions = + ' \n' + + ' \n' + + ' org.springframework.cloud\n' + + ' spring-cloud-starter-ribbon\n' + + ' \n' + + ' '; } this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-openfeign', null, exclusions); } @@ -250,7 +140,8 @@ module.exports = class extends BaseGenerator { } else if (this.buildTool === 'gradle') { if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { if (this.authenticationType === 'session') { - const content = 'compile \'org.springframework.cloud:spring-cloud-starter-openfeign\', { exclude group: \'org.springframework.cloud\', module: \'spring-cloud-starter-ribbon\' }'; + const content = + "compile 'org.springframework.cloud:spring-cloud-starter-openfeign', { exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-ribbon' }"; this.rewriteFile('./build.gradle', 'jhipster-needle-gradle-dependency', content); } else { this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-openfeign'); @@ -262,19 +153,31 @@ module.exports = class extends BaseGenerator { const mainClassFile = `${this.javaDir + this.getMainClassName()}.java`; if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { - this.rewriteFile(mainClassFile, 'import org.springframework.core.env.Environment;', 'import org.springframework.cloud.openfeign.EnableFeignClients;'); + this.rewriteFile( + mainClassFile, + 'import org.springframework.core.env.Environment;', + 'import org.springframework.cloud.openfeign.EnableFeignClients;' + ); } - this.rewriteFile(mainClassFile, 'import org.springframework.core.env.Environment;', 'import org.springframework.context.annotation.ComponentScan;'); + + this.rewriteFile( + mainClassFile, + 'import org.springframework.core.env.Environment;', + 'import org.springframework.context.annotation.ComponentScan;' + ); const componentScan = `${'@ComponentScan( excludeFilters = {\n' + - ' @ComponentScan.Filter('}${this.packageName}.client.ExcludeFromComponentScan.class)\n` + - '})'; + ' @ComponentScan.Filter('}${this.packageName}.client.ExcludeFromComponentScan.class)\n` + + '})'; this.rewriteFile(mainClassFile, '@SpringBootApplication', componentScan); if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { this.rewriteFile(mainClassFile, '@SpringBootApplication', '@EnableFeignClients'); } - this.template('src/main/java/package/client/_ExcludeFromComponentScan.java', `${this.javaDir}/client/ExcludeFromComponentScan.java`); + this.template( + 'src/main/java/package/client/_ExcludeFromComponentScan.java', + `${this.javaDir}/client/ExcludeFromComponentScan.java` + ); } }; } diff --git a/generators/openapi-cli/prompts.js b/generators/openapi-cli/prompts.js new file mode 100644 index 000000000000..c1e20e0ce1f8 --- /dev/null +++ b/generators/openapi-cli/prompts.js @@ -0,0 +1,254 @@ +/** + * Copyright 2013-2019 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 + * + * http://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. + */ +const path = require('path'); +const shelljs = require('shelljs'); +const request = require('sync-request'); + +module.exports = { + askActionType, + askExistingAvailableDocs, + askGenerationInfos +}; + +function fetchSwaggerResources(input) { + const availableDocs = []; + + const swaggerResources = request('GET', `${input}/swagger-resources`, { + // This header is needed to use the custom /swagger-resources controller + // and not the default one that has only the gateway's swagger resource + headers: { Accept: 'application/json, text/javascript;' } + }); + + JSON.parse(swaggerResources.getBody()).forEach(swaggerResource => { + availableDocs.push({ + value: { url: `${input}/${swaggerResource.location}`, name: swaggerResource.name }, + name: `${swaggerResource.name} (${swaggerResource.location})` + }); + }); + + return availableDocs; +} + +function askActionType() { + const done = this.async(); + + if (this.options.regen) { + return; + } + + const hasExistingApis = Object.keys(this.openApiClients).length !== 0; + + const actionList = [ + { + value: 'new', + name: 'Generate a new API client' + } + ]; + + if (hasExistingApis) { + actionList.push({ value: 'all', name: 'Generate all stored API clients' }); + actionList.push({ value: 'select', name: 'Select stored API clients to generate' }); + } + + const newClient = actionList.length === 1; + + const prompts = [ + { + when: !newClient, + type: 'list', + name: 'action', + message: 'What do you want to do ?', + choices: actionList + }, + { + when: response => response.action === 'new' || newClient, + type: 'list', + name: 'specOrigin', + message: 'Where do you want to import your OpenAPI/Swagger specification from ?', + choices: [ + { value: 'jhipster-endpoint', name: 'From a Jhipster /swagger-resources live doc endpoint' }, + { value: 'jhipster-directory', name: 'From the api.yml spec of an existing Jhipster project' }, + { value: 'custom-endpoint', name: 'From a custom specification file or endpoint' } + ] + }, + { + when: response => response.specOrigin === 'jhipster-endpoint', + type: 'input', + name: 'jhipsterEndpoint', + message: 'Enter the URL of the running Jhipster instance', + default: 'http://localhost:8080', + validate: input => { + try { + const availableDocs = fetchSwaggerResources(input); + + if (availableDocs.length === 0) { + return `No live doc found at ${input}`; + } + return true; + } catch (err) { + return `Error while fetching live doc from '${input}'. "${err.message}"`; + } + } + }, + { + when: response => response.specOrigin === 'jhipster-directory', + type: 'input', + name: 'jhipsterDirectory', + message: 'Enter the path to the jhipster project root directory', + default: '../', + validate: input => { + let fromPath; + if (path.isAbsolute(input)) { + fromPath = `${input}/src/main/resources/swagger/api.yml`; + } else { + fromPath = this.destinationPath(`${input}/src/main/resources/swagger/api.yml`); + } + + if (shelljs.test('-f', fromPath)) { + return true; + } + return `api.yml not found in ${input}/`; + } + }, + { + when: response => response.specOrigin === 'custom-endpoint', + type: 'input', + name: 'customEndpoint', + message: 'Where is your Swagger/OpenAPI spec (URL or path) ?', + default: 'http://petstore.swagger.io/v2/swagger.json', + store: true, + validate: input => { + try { + request('GET', `${input}`, { + // headers: { Accept: 'application/json, text/javascript;' } + }); + + return true; + } catch (err) { + return `Cannot read from ${input}`; + } + } + } + ]; + + this.prompt(prompts).then(props => { + if (props.jhipsterEndpoint !== undefined) { + props.availableDocs = fetchSwaggerResources(props.jhipsterEndpoint); + } else if (props.jhipsterDirectory !== undefined) { + props.inputSpec = `${props.jhipsterDirectory}/src/main/resources/swagger/api.yml`; + } else if (props.customEndpoint !== undefined) { + props.inputSpec = props.customEndpoint; + } + + if (newClient) { + props.action = 'new'; + } + + props.generatorName = 'spring'; + + this.props = props; + done(); + }); +} + +function askExistingAvailableDocs() { + const done = this.async(); + + const prompts = [ + { + when: this.props.availableDocs !== undefined, + type: 'list', + name: 'availableDoc', + message: 'Select the doc for which you want to create a client', + choices: this.props.availableDocs + } + ]; + + this.prompt(prompts).then(props => { + if (props.availableDoc !== undefined) { + this.props.inputSpec = props.availableDoc.url; + this.props.cliName = props.availableDoc.name; + } + done(); + }); +} + +function askGenerationInfos() { + const done = this.async(); + const prompts = [ + { + when: + this.props.specOrigin === 'jhipster-endpoint' && + this.config.get('serviceDiscoveryType') === 'eureka' && + this.props.generatorName === 'spring', + type: 'confirm', + name: 'useServiceDiscovery', + message: 'Do you want to use Eureka service discovery ?', + default: true + }, + { + when: response => this.props.action === 'new' && !response.useServiceDiscovery, + type: 'input', + name: 'cliName', + message: 'What is the unique name for your API client ?', + default: this.props.cliName || 'petstore', + validate: input => { + if (!/^([a-zA-Z0-9_]*)$/.test(input)) { + return 'Your API client name cannot contain special characters or a blank space'; + } + if (input === '') { + return 'Your API client name cannot be empty'; + } + return true; + } + }, + { + when: this.props.action === 'new', + type: 'confirm', + name: 'saveConfig', + message: 'Do you want to save this config for future reuse ?', + default: false + }, + { + when: this.props.action === 'select', + type: 'checkbox', + name: 'selected', + message: 'Select which APIs you want to generate', + choices: () => { + const choices = []; + Object.keys(this.openApiClients).forEach(cliName => { + choices.push({ + name: `${cliName} (${this.openApiClients[cliName].spec})`, + value: { cliName, spec: this.openApiClients[cliName] } + }); + }); + return choices; + } + } + ]; + + this.prompt(prompts).then(props => { + if (props.cliName !== undefined) { + this.props.cliName = props.cliName; + } + this.props.saveConfig = props.saveConfig; + this.props.selected = props.selected; + done(); + }); +} From 05727d9a4135badcf9bb4fff77168fd0131de2c5 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 2 May 2019 15:45:34 +0200 Subject: [PATCH 07/22] change module name to 'openapi-client' --- .vscode/launch.json | 4 ++-- cli/commands.js | 4 ++-- generators/openapi-cli/USAGE | 5 ----- generators/openapi-client/USAGE | 5 +++++ generators/{openapi-cli => openapi-client}/index.js | 2 +- generators/{openapi-cli => openapi-client}/prompts.js | 0 .../main/java/package/client/_ExcludeFromComponentScan.java | 0 .../libraries/spring-cloud/apiClient.mustache | 0 .../libraries/spring-cloud/clientConfiguration.mustache | 0 9 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 generators/openapi-cli/USAGE create mode 100644 generators/openapi-client/USAGE rename generators/{openapi-cli => openapi-client}/index.js (99%) rename generators/{openapi-cli => openapi-client}/prompts.js (100%) rename generators/{openapi-cli => openapi-client}/templates/src/main/java/package/client/_ExcludeFromComponentScan.java (100%) rename generators/{openapi-cli => openapi-client}/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache (100%) rename generators/{openapi-cli => openapi-client}/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 04325e311c85..6075e276b7a0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -278,10 +278,10 @@ { "type": "node", "request": "launch", - "name": "jhipster openapi-cli", + "name": "jhipster openapi-client", "program": "${workspaceFolder}/cli/jhipster.js", "args": [ - "openapi-cli" + "openapi-client" ], "cwd": "${workspaceFolder}/test-integration/samples/app-sample-dev/", "console": "integratedTerminal" diff --git a/cli/commands.js b/cli/commands.js index af9532686ddc..d4b21efe0744 100644 --- a/cli/commands.js +++ b/cli/commands.js @@ -135,8 +135,8 @@ Example: argument: ['name'], desc: 'Create a new Spring controller' }, - 'openapi-cli': { - desc: "Generates client code from an OpenAPI/Swagger definition" + 'openapi-client': { + desc: 'Generates java client code from an OpenAPI/Swagger definition' }, upgrade: { desc: 'Upgrade the JHipster version, and upgrade the generated application' diff --git a/generators/openapi-cli/USAGE b/generators/openapi-cli/USAGE deleted file mode 100644 index a2ea8630df37..000000000000 --- a/generators/openapi-cli/USAGE +++ /dev/null @@ -1,5 +0,0 @@ -Description: - Generates client code from an OpenAPI/Swagger definition - -Example: - jhipster openapi-cli diff --git a/generators/openapi-client/USAGE b/generators/openapi-client/USAGE new file mode 100644 index 000000000000..53935d5a365a --- /dev/null +++ b/generators/openapi-client/USAGE @@ -0,0 +1,5 @@ +Description: + Generates java client code from an OpenAPI/Swagger definition + +Example: + jhipster openapi-client diff --git a/generators/openapi-cli/index.js b/generators/openapi-client/index.js similarity index 99% rename from generators/openapi-cli/index.js rename to generators/openapi-client/index.js index 329e138590ea..a8c3e8b18df6 100644 --- a/generators/openapi-cli/index.js +++ b/generators/openapi-client/index.js @@ -183,6 +183,6 @@ module.exports = class extends BaseGenerator { } end() { - this.log('End of openapi-cli generator'); + this.log('End of openapi-client generator'); } }; diff --git a/generators/openapi-cli/prompts.js b/generators/openapi-client/prompts.js similarity index 100% rename from generators/openapi-cli/prompts.js rename to generators/openapi-client/prompts.js diff --git a/generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java b/generators/openapi-client/templates/src/main/java/package/client/_ExcludeFromComponentScan.java similarity index 100% rename from generators/openapi-cli/templates/src/main/java/package/client/_ExcludeFromComponentScan.java rename to generators/openapi-client/templates/src/main/java/package/client/_ExcludeFromComponentScan.java diff --git a/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache b/generators/openapi-client/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache similarity index 100% rename from generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache rename to generators/openapi-client/templates/swagger-codegen/libraries/spring-cloud/apiClient.mustache diff --git a/generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache b/generators/openapi-client/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache similarity index 100% rename from generators/openapi-cli/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache rename to generators/openapi-client/templates/swagger-codegen/libraries/spring-cloud/clientConfiguration.mustache From 946851c860b5606a196a6cd05741f14aa3037bc1 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Mon, 20 May 2019 12:41:08 +0200 Subject: [PATCH 08/22] move writing phases to files.js --- generators/openapi-client/files.js | 144 +++++++++++++++++++++++++++++ generators/openapi-client/index.js | 102 +------------------- 2 files changed, 147 insertions(+), 99 deletions(-) create mode 100644 generators/openapi-client/files.js diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js new file mode 100644 index 000000000000..39a94de8aa96 --- /dev/null +++ b/generators/openapi-client/files.js @@ -0,0 +1,144 @@ +/** + * Copyright 2013-2019 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 + * + * http://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. + */ + +const path = require('path'); +const shelljs = require('shelljs'); +const s = require('underscore.string'); +const _ = require('lodash'); +const chalk = require('chalk'); +const jhipsterConstants = require('../generator-constants'); + +module.exports = { + writeFiles +}; + +function writeFiles() { + return { + callOpenApiGenerator() { + this.baseName = this.config.get('baseName'); + this.authenticationType = this.config.get('authenticationType'); + this.packageName = this.config.get('packageName'); + this.clientPackageManager = this.config.get('clientPackageManager'); + this.packageFolder = this.config.get('packageFolder'); + this.buildTool = this.config.get('buildTool'); + + this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; + + Object.keys(this.clientsToGenerate).forEach(cliName => { + const inputSpec = this.clientsToGenerate[cliName].spec; + const generatorName = this.clientsToGenerate[cliName].generatorName; + let execLine; + if (generatorName === 'spring') { + const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; + this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); + + execLine = + `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis ` + + '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ' + + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + + ` -i ${inputSpec} --artifact-id ${s.camelize(cliName)} --api-package ${cliPackage}.api` + + ` --model-package ${cliPackage}.model` + + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + + ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},` + + `title=${s.camelize(cliName)}`; + if (this.clientsToGenerate[cliName].useServiceDiscovery) { + execLine += ' --additional-properties ribbon=true'; + } + } + this.log(execLine); + shelljs.exec(execLine); + }); + }, + + addBackendDependencies() { + if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + return; + } + + if (this.buildTool === 'maven') { + if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { + let exclusions; + if (this.authenticationType === 'session') { + exclusions = + ' \n' + + ' \n' + + ' org.springframework.cloud\n' + + ' spring-cloud-starter-ribbon\n' + + ' \n' + + ' '; + } + this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-openfeign', null, exclusions); + } + this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-oauth2'); + } else if (this.buildTool === 'gradle') { + if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { + if (this.authenticationType === 'session') { + const content = + "compile 'org.springframework.cloud:spring-cloud-starter-openfeign', { exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-ribbon' }"; + this.rewriteFile('./build.gradle', 'jhipster-needle-gradle-dependency', content); + } else { + this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-openfeign'); + } + } + this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-oauth2'); + } + }, + + enableFeignClients() { + if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + return; + } + + const mainClassFile = `${this.javaDir + this.getMainClassName()}.java`; + + if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { + this.rewriteFile( + mainClassFile, + 'import org.springframework.core.env.Environment;', + 'import org.springframework.cloud.openfeign.EnableFeignClients;' + ); + this.rewriteFile(mainClassFile, '@SpringBootApplication', '@EnableFeignClients'); + } + }, + + handleComponentScanExclusion() { + if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + return; + } + + const mainClassFile = `${this.javaDir + this.getMainClassName()}.java`; + + this.rewriteFile( + mainClassFile, + 'import org.springframework.core.env.Environment;', + 'import org.springframework.context.annotation.ComponentScan;' + ); + + const componentScan = + `${'@ComponentScan( excludeFilters = {\n @ComponentScan.Filter('}${this.packageName}` + + '.client.ExcludeFromComponentScan.class)\n})'; + this.rewriteFile(mainClassFile, '@SpringBootApplication', componentScan); + + this.template( + 'src/main/java/package/client/_ExcludeFromComponentScan.java', + `${this.javaDir}/client/ExcludeFromComponentScan.java` + ); + } + }; +} diff --git a/generators/openapi-client/index.js b/generators/openapi-client/index.js index a8c3e8b18df6..7ea9d44fe086 100644 --- a/generators/openapi-client/index.js +++ b/generators/openapi-client/index.js @@ -16,13 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const path = require('path'); -const shelljs = require('shelljs'); -const _ = require('underscore.string'); + const chalk = require('chalk'); const BaseGenerator = require('../generator-base'); -const jhipsterConstants = require('../generator-constants'); const prompts = require('./prompts'); +const writeFiles = require('./files').writeFiles; module.exports = class extends BaseGenerator { constructor(args, opts) { @@ -85,101 +83,7 @@ module.exports = class extends BaseGenerator { } get writing() { - return { - callOpenApiGenerator() { - this.baseName = this.config.get('baseName'); - this.authenticationType = this.config.get('authenticationType'); - this.packageName = this.config.get('packageName'); - this.clientPackageManager = this.config.get('clientPackageManager'); - this.packageFolder = this.config.get('packageFolder'); - this.buildTool = this.config.get('buildTool'); - - this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; - - Object.keys(this.clientsToGenerate).forEach(cliName => { - const inputSpec = this.clientsToGenerate[cliName].spec; - const generatorName = this.clientsToGenerate[cliName].generatorName; - let execLine; - if (generatorName === 'spring') { - const cliPackage = `${this.packageName}.client.${_.underscored(cliName)}`; - this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); - - execLine = - `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis ` + - '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ' + - ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + - ` -i ${inputSpec} --artifact-id ${_.camelize(cliName)} --api-package ${cliPackage}.api` + - ` --model-package ${cliPackage}.model` + - ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + - ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},title=${_.camelize(cliName)}`; - if (this.clientsToGenerate[cliName].useServiceDiscovery) { - execLine += ' --additional-properties ribbon=true'; - } - } - this.log(execLine); - shelljs.exec(execLine); - }); - }, - - writeTemplates() { - if (this.buildTool === 'maven') { - if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { - let exclusions; - if (this.authenticationType === 'session') { - exclusions = - ' \n' + - ' \n' + - ' org.springframework.cloud\n' + - ' spring-cloud-starter-ribbon\n' + - ' \n' + - ' '; - } - this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-openfeign', null, exclusions); - } - this.addMavenDependency('org.springframework.cloud', 'spring-cloud-starter-oauth2'); - } else if (this.buildTool === 'gradle') { - if (!['microservice', 'gateway', 'uaa'].includes(this.applicationType)) { - if (this.authenticationType === 'session') { - const content = - "compile 'org.springframework.cloud:spring-cloud-starter-openfeign', { exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-ribbon' }"; - this.rewriteFile('./build.gradle', 'jhipster-needle-gradle-dependency', content); - } else { - this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-openfeign'); - } - } - this.addGradleDependency('compile', 'org.springframework.cloud', 'spring-cloud-starter-oauth2'); - } - - const mainClassFile = `${this.javaDir + this.getMainClassName()}.java`; - - if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { - this.rewriteFile( - mainClassFile, - 'import org.springframework.core.env.Environment;', - 'import org.springframework.cloud.openfeign.EnableFeignClients;' - ); - } - - this.rewriteFile( - mainClassFile, - 'import org.springframework.core.env.Environment;', - 'import org.springframework.context.annotation.ComponentScan;' - ); - - const componentScan = `${'@ComponentScan( excludeFilters = {\n' + - ' @ComponentScan.Filter('}${this.packageName}.client.ExcludeFromComponentScan.class)\n` + - '})'; - this.rewriteFile(mainClassFile, '@SpringBootApplication', componentScan); - - if (this.applicationType !== 'microservice' || !['uaa', 'jwt'].includes(this.authenticationType)) { - this.rewriteFile(mainClassFile, '@SpringBootApplication', '@EnableFeignClients'); - } - this.template( - 'src/main/java/package/client/_ExcludeFromComponentScan.java', - `${this.javaDir}/client/ExcludeFromComponentScan.java` - ); - } - }; + return writeFiles(); } end() { From 641d6319fb44cb840be302c3261f76b80d7d296f Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 23 May 2019 15:22:32 +0200 Subject: [PATCH 09/22] generates java files only with spring clients --- generators/openapi-client/files.js | 18 ++++++++++++++---- generators/openapi-client/index.js | 10 ++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index 39a94de8aa96..6ddd97baa6f1 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -62,12 +62,22 @@ function writeFiles() { } } this.log(execLine); - shelljs.exec(execLine); + + const done = this.async(); + shelljs.exec(execLine, { silent: this.silent }, (code, msg, err) => { + if (code === 0) { + this.success(`Succesfully generated ${cliName} ${generatorName} client`); + done(); + } else { + this.error(`Something went wrong while generating ${cliName} ${generatorName} client: ${msg} ${err}`); + done(); + } + }); }); }, addBackendDependencies() { - if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + if (!_.map(this.clientsToGenerate, 'generatorName').includes('spring')) { return; } @@ -101,7 +111,7 @@ function writeFiles() { }, enableFeignClients() { - if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + if (!_.map(this.clientsToGenerate, 'generatorName').includes('spring')) { return; } @@ -118,7 +128,7 @@ function writeFiles() { }, handleComponentScanExclusion() { - if (!['spring'].includes(_.map(this.clientsToGenerate, 'generatorName'))) { + if (!_.map(this.clientsToGenerate, 'generatorName').includes('spring')) { return; } diff --git a/generators/openapi-client/index.js b/generators/openapi-client/index.js index 7ea9d44fe086..9de41025054a 100644 --- a/generators/openapi-client/index.js +++ b/generators/openapi-client/index.js @@ -25,7 +25,6 @@ const writeFiles = require('./files').writeFiles; module.exports = class extends BaseGenerator { constructor(args, opts) { super(args, opts); - // This adds support for a `--from-cli` flag this.option('regen', { desc: 'Regenerates all saved clients', type: Boolean, @@ -36,12 +35,15 @@ module.exports = class extends BaseGenerator { get initializing() { return { - getConfig() { - this.openApiClients = this.config.get('openApiClients') || {}; + validateFromCli() { + this.checkInvocationFromCLI(); }, - displayLogo() { + sayHello() { // Have Yeoman greet the user. this.log(chalk.white('Welcome to the JHipster OpenApi client Sub-Generator')); + }, + getConfig() { + this.openApiClients = this.config.get('openApiClients') || {}; } }; } From ee30c19acfec605c8664d20429e56e565f8b5520 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 07:44:34 +0200 Subject: [PATCH 10/22] remove package.json openapi-generator, use openapi-generator-cli jar --- .../client/templates/angular/package.json.ejs | 2 +- .../client/templates/react/package.json.ejs | 2 +- generators/openapi-client/files.js | 26 ++++++++++++++----- generators/server/templates/package.json.ejs | 6 ++--- package.json | 1 - 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/generators/client/templates/angular/package.json.ejs b/generators/client/templates/angular/package.json.ejs index 76c5875cfbab..08facef2a850 100644 --- a/generators/client/templates/angular/package.json.ejs +++ b/generators/client/templates/angular/package.json.ejs @@ -146,6 +146,7 @@ <%_ if (protractorTests) { _%> "webdriver-manager": "12.1.6", <%_ } _%> + "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", "webpack": "4.39.3", "webpack-cli": "3.3.7", "webpack-dev-server": "3.8.0", @@ -176,7 +177,6 @@ "build": "<%= clientPackageManager %> run webpack:prod", "test": "<%= clientPackageManager %> run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js", "test:watch": "<%= clientPackageManager %> run test <%= optionsForwarder %>--watch", - "openapi-generator": "openapi-generator", "webpack:dev": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal", "webpack:dev-verbose": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal", "webpack:build:main": "<%= clientPackageManager %> run webpack <%= optionsForwarder %>--config webpack/webpack.dev.js --env.stats=minimal", diff --git a/generators/client/templates/react/package.json.ejs b/generators/client/templates/react/package.json.ejs index 1f7d2c1c79af..9b72861f1282 100644 --- a/generators/client/templates/react/package.json.ejs +++ b/generators/client/templates/react/package.json.ejs @@ -164,6 +164,7 @@ limitations under the License. <%_ if (protractorTests) { _%> "webdriver-manager": "12.1.5", <%_ } _%> + "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", "webpack": "4.28.4", "webpack-cli": "3.3.0", "webpack-dev-server": "3.2.1", @@ -197,7 +198,6 @@ limitations under the License. "test": "<%= clientPackageManager %> run lint && <%= clientPackageManager %> run jest", "test-ci": "<%= clientPackageManager %> run lint && <%= clientPackageManager %> run jest:update", "test:watch": "<%= clientPackageManager %> test <%= optionsForwarder %>--watch", - "openapi-generator": "openapi-generator", "webpack:dev": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --port=9060 --env.stats=minimal", "webpack:dev-verbose": "<%= clientPackageManager %> run webpack-dev-server <%= optionsForwarder %>--config webpack/webpack.dev.js --inline --port=9060 --profile --progress --env.stats=normal", "webpack:build:main": "<%= clientPackageManager %> run webpack <%= optionsForwarder %>--config webpack/webpack.dev.js --env.stats=minimal", diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index 6ddd97baa6f1..a50e2d38a39c 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -43,14 +43,26 @@ function writeFiles() { Object.keys(this.clientsToGenerate).forEach(cliName => { const inputSpec = this.clientsToGenerate[cliName].spec; const generatorName = this.clientsToGenerate[cliName].generatorName; - let execLine; + + const jarPath = path.resolve( + __dirname, + '../../', + 'node_modules', + '@openapitools', + 'openapi-generator-cli', + 'bin', + 'openapi-generator.jar' + ); + const JAVA_OPTS = process.env.JAVA_OPTS || ''; + let command = `java ${JAVA_OPTS} -jar "${jarPath}"`; + if (generatorName === 'spring') { const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); - execLine = - `${this.clientPackageManager} run openapi-generator -- generate -g spring -Dmodels -Dapis ` + - '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java ' + + command += + ' generate -g spring -Dmodels -Dapis ' + + '-DsupportingFiles ' + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + ` -i ${inputSpec} --artifact-id ${s.camelize(cliName)} --api-package ${cliPackage}.api` + ` --model-package ${cliPackage}.model` + @@ -58,13 +70,13 @@ function writeFiles() { ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},` + `title=${s.camelize(cliName)}`; if (this.clientsToGenerate[cliName].useServiceDiscovery) { - execLine += ' --additional-properties ribbon=true'; + command += ' --additional-properties ribbon=true'; } } - this.log(execLine); + this.log(command); const done = this.async(); - shelljs.exec(execLine, { silent: this.silent }, (code, msg, err) => { + shelljs.exec(command, { silent: this.silent }, (code, msg, err) => { if (code === 0) { this.success(`Succesfully generated ${cliName} ${generatorName} client`); done(); diff --git a/generators/server/templates/package.json.ejs b/generators/server/templates/package.json.ejs index e3969b90eb53..4d6a5cc9526b 100644 --- a/generators/server/templates/package.json.ejs +++ b/generators/server/templates/package.json.ejs @@ -29,10 +29,8 @@ <%_ otherModules.forEach(module => { _%> "<%= module.name %>": "<%= module.version %>", <%_ }); _%> - "generator-jhipster": "<%= jhipsterVersion %>" - }, - scripts: { - "openapi-generator": "openapi-generator" + "generator-jhipster": "<%= jhipsterVersion %>", + "@openapitools/openapi-generator-cli": "0.0.9-3.3.4" }, "engines": { "node": ">=8.9.0" diff --git a/package.json b/package.json index feca438472c2..534130ee6cb0 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "uuid": "3.3.2", "sync-request": "6.0.0", "underscore.string": "3.3.4", - "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", "yeoman-environment": "2.3.4", "yeoman-generator": "3.2.0", "yo": "3.1.0" From 68d6a408358af4f5bac90f9346211efee3f5fff5 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 07:44:58 +0200 Subject: [PATCH 11/22] add test for openapi-client subgenerator --- test/openapi-client.spec.js | 45 ++++++++ .../microservice-simple/.yo-rc.json | 26 +++++ .../openapi-client/petstore-openapi-3.yml | 109 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 test/openapi-client.spec.js create mode 100644 test/templates/openapi-client/microservice-simple/.yo-rc.json create mode 100644 test/templates/openapi-client/petstore-openapi-3.yml diff --git a/test/openapi-client.spec.js b/test/openapi-client.spec.js new file mode 100644 index 000000000000..fdc81baeafff --- /dev/null +++ b/test/openapi-client.spec.js @@ -0,0 +1,45 @@ +const path = require('path'); +const assert = require('yeoman-assert'); +const helpers = require('yeoman-test'); +const fse = require('fs-extra'); + +const basePackage = 'src/main/java/com/mycompany/myapp'; +const expectedFiles = { + generatedJava: [ + `${basePackage}/client/petstore/ApiKeyRequestInterceptor.java`, + `${basePackage}/client/petstore/ClientConfiguration.java` + ] +}; + +describe('JHipster OpenAPI Client Sub Generator', () => { + //-------------------------------------------------- + // Jenkins tests + //-------------------------------------------------- + describe('Spring: microservice petstore openapi 3 ', () => { + before(done => { + helpers + .run(require.resolve('../generators/openapi-client')) + .inTmpDir(dir => { + fse.copySync(path.join(__dirname, './templates/openapi-client/microservice-simple'), dir); + fse.copySync(path.join(__dirname, './templates/openapi-client'), dir); + }) + .withOptions({ skipChecks: true }) + .withPrompts({ + action: 'new', + specOrigin: 'custom-endpoint', + customEndpoint: 'petstore-openapi-3.yml', + cliName: 'petstore' + }) + .on('end', done); + }); + it('creates java supporting files', () => { + assert.file(expectedFiles.generatedJava); + }); + it('does not generate useless supporting files', () => { + assert.noFile('.openapi-generator/VERSION'); + }); + it('does not override README.md file', () => { + assert.fileContent('README.md', /www\.jhipster\.tech/); + }); + }); +}); diff --git a/test/templates/openapi-client/microservice-simple/.yo-rc.json b/test/templates/openapi-client/microservice-simple/.yo-rc.json new file mode 100644 index 000000000000..9a01d7dc2924 --- /dev/null +++ b/test/templates/openapi-client/microservice-simple/.yo-rc.json @@ -0,0 +1,26 @@ +{ + "generator-jhipster": { + "applicationType": "microservice", + "baseName": "sampleOpenApiClient", + "packageName": "com.mycompany.myapp", + "packageFolder": "com/mycompany/myapp", + "serverPort": "8081", + "authenticationType": "jwt", + "cacheProvider": "ehcache", + "enableHibernateCache": true, + "websocket": false, + "databaseType": "sql", + "devDatabaseType": "h2Memory", + "prodDatabaseType": "mysql", + "searchEngine": false, + "messageBroker": false, + "serviceDiscoveryType": "consul", + "buildTool": "maven", + "enableSwaggerCodegen": true, + "jwtSecretKey": "Y2Q4NzYwMzEwMzNiZTExYzI1Y2E2Yjg2MGViNjRjMGE2YjcwNTEzY2ZkMDgxNTYwNTUxZWJjMzJhYjUwY2JiOTBiNjk3YWExMWRiNWM0MDljOTJhN2M2ODBkZDY5MjQ4MTVhYmUyZGUwYjFiODE1YmFhMTE0MjY2NDRiZDI5NDI=", + "testFrameworks": [], + "enableTranslation": false, + "skipClient": true, + "skipUserManagement": true + } +} \ No newline at end of file diff --git a/test/templates/openapi-client/petstore-openapi-3.yml b/test/templates/openapi-client/petstore-openapi-3.yml new file mode 100644 index 000000000000..5b2c589608b2 --- /dev/null +++ b/test/templates/openapi-client/petstore-openapi-3.yml @@ -0,0 +1,109 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file From 0b5c0599adf5a1247bbbb15255cbe239e12da577 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 09:21:24 +0200 Subject: [PATCH 12/22] fix url composition when reading springfox swagger endpoint list --- generators/openapi-client/prompts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/generators/openapi-client/prompts.js b/generators/openapi-client/prompts.js index c1e20e0ce1f8..f48bc15f97d4 100644 --- a/generators/openapi-client/prompts.js +++ b/generators/openapi-client/prompts.js @@ -36,8 +36,10 @@ function fetchSwaggerResources(input) { }); JSON.parse(swaggerResources.getBody()).forEach(swaggerResource => { + const baseUrl = input.replace(/\/$/, ''); + const specPath = swaggerResource.location.replace(/^\/+/g, ''); availableDocs.push({ - value: { url: `${input}/${swaggerResource.location}`, name: swaggerResource.name }, + value: { url: `${baseUrl}/${specPath}`, name: swaggerResource.name }, name: `${swaggerResource.name} (${swaggerResource.location})` }); }); From af1426b2e71114aa1dd22e7e7f21c87e1cc51d17 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 10:47:24 +0200 Subject: [PATCH 13/22] fix supportingFiles generation and fix test --- generators/openapi-client/files.js | 25 ++++++++++++++++--------- test/openapi-client.spec.js | 22 ++++++++++++---------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index a50e2d38a39c..1a72e6e99855 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -53,25 +53,32 @@ function writeFiles() { 'bin', 'openapi-generator.jar' ); - const JAVA_OPTS = process.env.JAVA_OPTS || ''; - let command = `java ${JAVA_OPTS} -jar "${jarPath}"`; - + let JAVA_OPTS = process.env.JAVA_OPTS || ''; + let command; if (generatorName === 'spring') { const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); - command += - ' generate -g spring -Dmodels -Dapis ' + - '-DsupportingFiles ' + - ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} --library spring-cloud ` + + JAVA_OPTS += + ' -Dmodels -Dapis ' + + '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java '; + + let params = + ' generate -g spring ' + + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} ` + + ' --library spring-cloud ' + ` -i ${inputSpec} --artifact-id ${s.camelize(cliName)} --api-package ${cliPackage}.api` + ` --model-package ${cliPackage}.model` + - ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate ' + + ' --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},` + `title=${s.camelize(cliName)}`; + if (this.clientsToGenerate[cliName].useServiceDiscovery) { - command += ' --additional-properties ribbon=true'; + params += ' --additional-properties ribbon=true'; } + + command = `java ${JAVA_OPTS} -jar ${jarPath} ${params}`; } this.log(command); diff --git a/test/openapi-client.spec.js b/test/openapi-client.spec.js index fdc81baeafff..cf2e302e968b 100644 --- a/test/openapi-client.spec.js +++ b/test/openapi-client.spec.js @@ -5,15 +5,19 @@ const fse = require('fs-extra'); const basePackage = 'src/main/java/com/mycompany/myapp'; const expectedFiles = { - generatedJava: [ + petstoreClientFiles: [ `${basePackage}/client/petstore/ApiKeyRequestInterceptor.java`, - `${basePackage}/client/petstore/ClientConfiguration.java` + `${basePackage}/client/petstore/ClientConfiguration.java`, + `${basePackage}/client/petstore/api/PetsApi.java`, + `${basePackage}/client/petstore/api/PetsApiClient.java`, + `${basePackage}/client/petstore/model/Error.java`, + `${basePackage}/client/petstore/model/Pet.java` ] }; describe('JHipster OpenAPI Client Sub Generator', () => { //-------------------------------------------------- - // Jenkins tests + // Spring Cloud Client tests //-------------------------------------------------- describe('Spring: microservice petstore openapi 3 ', () => { before(done => { @@ -32,14 +36,12 @@ describe('JHipster OpenAPI Client Sub Generator', () => { }) .on('end', done); }); - it('creates java supporting files', () => { - assert.file(expectedFiles.generatedJava); + it('creates java client files', () => { + assert.file(expectedFiles.petstoreClientFiles); }); - it('does not generate useless supporting files', () => { - assert.noFile('.openapi-generator/VERSION'); - }); - it('does not override README.md file', () => { - assert.fileContent('README.md', /www\.jhipster\.tech/); + it('does not override Jhipster files ', () => { + assert.noFile('README.md'); + assert.noFile('pom.xml'); }); }); }); From c7d29f5a739b12de7801d887afb1fd9d11096049 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 12:10:22 +0200 Subject: [PATCH 14/22] use openapi jar file in the node_modules of the generated project --- generators/openapi-client/files.js | 17 ++++------------- package.json | 3 ++- test/openapi-client.spec.js | 1 + 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index 1a72e6e99855..f191f902ce7c 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -44,24 +44,15 @@ function writeFiles() { const inputSpec = this.clientsToGenerate[cliName].spec; const generatorName = this.clientsToGenerate[cliName].generatorName; - const jarPath = path.resolve( - __dirname, - '../../', - 'node_modules', - '@openapitools', - 'openapi-generator-cli', - 'bin', - 'openapi-generator.jar' - ); - let JAVA_OPTS = process.env.JAVA_OPTS || ''; + // using openapi jar file since so this section can be tested + const jarPath = path.resolve('node_modules', '@openapitools', 'openapi-generator-cli', 'bin', 'openapi-generator.jar'); + let JAVA_OPTS; let command; if (generatorName === 'spring') { const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); - JAVA_OPTS += - ' -Dmodels -Dapis ' + - '-DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java '; + JAVA_OPTS = ' -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java '; let params = ' generate -g spring ' + diff --git a/package.json b/package.json index 534130ee6cb0..44f7bef6ce91 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "mocha": "6.1.4", "sinon": "7.2.5", "yeoman-assert": "3.1.1", - "yeoman-test": "1.9.1" + "yeoman-test": "1.9.1", + "@openapitools/openapi-generator-cli": "0.0.9-3.3.4" }, "resolutions": { "inquirer": "6.3.1" diff --git a/test/openapi-client.spec.js b/test/openapi-client.spec.js index cf2e302e968b..fd91ed50dda9 100644 --- a/test/openapi-client.spec.js +++ b/test/openapi-client.spec.js @@ -26,6 +26,7 @@ describe('JHipster OpenAPI Client Sub Generator', () => { .inTmpDir(dir => { fse.copySync(path.join(__dirname, './templates/openapi-client/microservice-simple'), dir); fse.copySync(path.join(__dirname, './templates/openapi-client'), dir); + fse.copySync(path.join(__dirname, '../node_modules/@openapitools'), `${dir}/node_modules/@openapitools`); }) .withOptions({ skipChecks: true }) .withPrompts({ From 807b7b421d6a4c4ba23158dbb012f61083b6a0b7 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 12:50:53 +0200 Subject: [PATCH 15/22] fix --regen option --- generators/openapi-client/files.js | 5 +++++ generators/openapi-client/prompts.js | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index f191f902ce7c..9037316e37dc 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -40,6 +40,11 @@ function writeFiles() { this.javaDir = `${jhipsterConstants.SERVER_MAIN_SRC_DIR + this.packageFolder}/`; + if (Object.keys(this.clientsToGenerate).length === 0) { + this.log('No openapi client configured. Please run "jhipster openapi-client" to generate your first OpenAPI client.'); + return; + } + Object.keys(this.clientsToGenerate).forEach(cliName => { const inputSpec = this.clientsToGenerate[cliName].spec; const generatorName = this.clientsToGenerate[cliName].generatorName; diff --git a/generators/openapi-client/prompts.js b/generators/openapi-client/prompts.js index f48bc15f97d4..a5acbb024caf 100644 --- a/generators/openapi-client/prompts.js +++ b/generators/openapi-client/prompts.js @@ -48,12 +48,12 @@ function fetchSwaggerResources(input) { } function askActionType() { - const done = this.async(); - if (this.options.regen) { return; } + const done = this.async(); + const hasExistingApis = Object.keys(this.openApiClients).length !== 0; const actionList = [ @@ -170,11 +170,14 @@ function askActionType() { } function askExistingAvailableDocs() { + if (this.options.regen) { + return; + } const done = this.async(); const prompts = [ { - when: this.props.availableDocs !== undefined, + when: !this.options.regen && this.props.availableDocs !== undefined, type: 'list', name: 'availableDoc', message: 'Select the doc for which you want to create a client', @@ -192,6 +195,10 @@ function askExistingAvailableDocs() { } function askGenerationInfos() { + if (this.options.regen) { + return; + } + const done = this.async(); const prompts = [ { From 1d719c909f54e9008de32cfbf4b78ed38d2562ab Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 14:08:13 +0200 Subject: [PATCH 16/22] update openapi-generator-cli to version 0.0.14-4.0.2 --- generators/client/templates/angular/package.json.ejs | 2 +- generators/client/templates/react/package.json.ejs | 2 +- generators/server/templates/package.json.ejs | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/generators/client/templates/angular/package.json.ejs b/generators/client/templates/angular/package.json.ejs index 08facef2a850..9b06f2e1e8c3 100644 --- a/generators/client/templates/angular/package.json.ejs +++ b/generators/client/templates/angular/package.json.ejs @@ -146,7 +146,7 @@ <%_ if (protractorTests) { _%> "webdriver-manager": "12.1.6", <%_ } _%> - "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", + "@openapitools/openapi-generator-cli": "0.0.14-4.0.2", "webpack": "4.39.3", "webpack-cli": "3.3.7", "webpack-dev-server": "3.8.0", diff --git a/generators/client/templates/react/package.json.ejs b/generators/client/templates/react/package.json.ejs index 9b72861f1282..312444ec461d 100644 --- a/generators/client/templates/react/package.json.ejs +++ b/generators/client/templates/react/package.json.ejs @@ -164,7 +164,7 @@ limitations under the License. <%_ if (protractorTests) { _%> "webdriver-manager": "12.1.5", <%_ } _%> - "@openapitools/openapi-generator-cli": "0.0.9-3.3.4", + "@openapitools/openapi-generator-cli": "0.0.14-4.0.2", "webpack": "4.28.4", "webpack-cli": "3.3.0", "webpack-dev-server": "3.2.1", diff --git a/generators/server/templates/package.json.ejs b/generators/server/templates/package.json.ejs index 4d6a5cc9526b..c02951e2394f 100644 --- a/generators/server/templates/package.json.ejs +++ b/generators/server/templates/package.json.ejs @@ -30,7 +30,7 @@ "<%= module.name %>": "<%= module.version %>", <%_ }); _%> "generator-jhipster": "<%= jhipsterVersion %>", - "@openapitools/openapi-generator-cli": "0.0.9-3.3.4" + "@openapitools/openapi-generator-cli": "0.0.14-4.0.2" }, "engines": { "node": ">=8.9.0" diff --git a/package.json b/package.json index 44f7bef6ce91..829e74e31cf9 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "sinon": "7.2.5", "yeoman-assert": "3.1.1", "yeoman-test": "1.9.1", - "@openapitools/openapi-generator-cli": "0.0.9-3.3.4" + "@openapitools/openapi-generator-cli": "0.0.14-4.0.2" }, "resolutions": { "inquirer": "6.3.1" From e95a2393fc4fe1cf2d945e5d006133247f1b7d33 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Thu, 20 Jun 2019 20:10:29 +0200 Subject: [PATCH 17/22] test for regeneration and component scan exclusion --- test/openapi-client.spec.js | 39 ++++++++++++++++++- .../microservice-with-client/.yo-rc.json | 32 +++++++++++++++ .../monolith-simple/.yo-rc.json | 26 +++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 test/templates/openapi-client/microservice-with-client/.yo-rc.json create mode 100644 test/templates/openapi-client/monolith-simple/.yo-rc.json diff --git a/test/openapi-client.spec.js b/test/openapi-client.spec.js index fd91ed50dda9..812840e0caec 100644 --- a/test/openapi-client.spec.js +++ b/test/openapi-client.spec.js @@ -19,7 +19,7 @@ describe('JHipster OpenAPI Client Sub Generator', () => { //-------------------------------------------------- // Spring Cloud Client tests //-------------------------------------------------- - describe('Spring: microservice petstore openapi 3 ', () => { + describe('Spring: microservice petstore custom endpoint ', () => { before(done => { helpers .run(require.resolve('../generators/openapi-client')) @@ -40,9 +40,46 @@ describe('JHipster OpenAPI Client Sub Generator', () => { it('creates java client files', () => { assert.file(expectedFiles.petstoreClientFiles); }); + it('generates file for component scan exclusion', () => { + assert.file(`${basePackage}/client/ExcludeFromComponentScan.java`); + }); it('does not override Jhipster files ', () => { assert.noFile('README.md'); assert.noFile('pom.xml'); }); }); + + describe('Spring: microservice petstore regenerate ', () => { + before(done => { + helpers + .run(require.resolve('../generators/openapi-client')) + .inTmpDir(dir => { + fse.copySync(path.join(__dirname, './templates/openapi-client/microservice-with-client'), dir); + fse.copySync(path.join(__dirname, './templates/openapi-client'), dir); + fse.copySync(path.join(__dirname, '../node_modules/@openapitools'), `${dir}/node_modules/@openapitools`); + }) + .withOptions({ skipChecks: true, regen: true }) + .on('end', done); + }); + it('regenerates java client files', () => { + assert.file(expectedFiles.petstoreClientFiles); + }); + }); + + describe('Spring: microservice regenerate no clients ', () => { + before(done => { + helpers + .run(require.resolve('../generators/openapi-client')) + .inTmpDir(dir => { + fse.copySync(path.join(__dirname, './templates/openapi-client/microservice-simple'), dir); + fse.copySync(path.join(__dirname, './templates/openapi-client'), dir); + fse.copySync(path.join(__dirname, '../node_modules/@openapitools'), `${dir}/node_modules/@openapitools`); + }) + .withOptions({ skipChecks: true, regen: true }) + .on('end', done); + }); + it('does not generate java client if no client configured', () => { + assert.noFile(expectedFiles.petstoreClientFiles); + }); + }); }); diff --git a/test/templates/openapi-client/microservice-with-client/.yo-rc.json b/test/templates/openapi-client/microservice-with-client/.yo-rc.json new file mode 100644 index 000000000000..5fe675bc5e9e --- /dev/null +++ b/test/templates/openapi-client/microservice-with-client/.yo-rc.json @@ -0,0 +1,32 @@ +{ + "generator-jhipster": { + "applicationType": "microservice", + "baseName": "sampleOpenApiClient", + "packageName": "com.mycompany.myapp", + "packageFolder": "com/mycompany/myapp", + "serverPort": "8081", + "authenticationType": "jwt", + "cacheProvider": "ehcache", + "enableHibernateCache": true, + "websocket": false, + "databaseType": "sql", + "devDatabaseType": "h2Memory", + "prodDatabaseType": "mysql", + "searchEngine": false, + "messageBroker": false, + "serviceDiscoveryType": "consul", + "buildTool": "maven", + "enableSwaggerCodegen": true, + "jwtSecretKey": "Y2Q4NzYwMzEwMzNiZTExYzI1Y2E2Yjg2MGViNjRjMGE2YjcwNTEzY2ZkMDgxNTYwNTUxZWJjMzJhYjUwY2JiOTBiNjk3YWExMWRiNWM0MDljOTJhN2M2ODBkZDY5MjQ4MTVhYmUyZGUwYjFiODE1YmFhMTE0MjY2NDRiZDI5NDI=", + "testFrameworks": [], + "enableTranslation": false, + "skipClient": true, + "skipUserManagement": true, + "openApiClients": { + "petstore": { + "spec": "petstore-openapi-3.yml", + "generatorName": "spring" + } + } + } +} \ No newline at end of file diff --git a/test/templates/openapi-client/monolith-simple/.yo-rc.json b/test/templates/openapi-client/monolith-simple/.yo-rc.json new file mode 100644 index 000000000000..45bbec1f8c58 --- /dev/null +++ b/test/templates/openapi-client/monolith-simple/.yo-rc.json @@ -0,0 +1,26 @@ +{ + "generator-jhipster": { + "applicationType": "monolith", + "baseName": "sampleOpenApiClient", + "packageName": "com.mycompany.myapp", + "packageFolder": "com/mycompany/myapp", + "serverPort": "8081", + "authenticationType": "jwt", + "cacheProvider": "ehcache", + "enableHibernateCache": true, + "websocket": false, + "databaseType": "sql", + "devDatabaseType": "h2Memory", + "prodDatabaseType": "mysql", + "searchEngine": false, + "messageBroker": false, + "serviceDiscoveryType": "consul", + "buildTool": "maven", + "enableSwaggerCodegen": true, + "jwtSecretKey": "Y2Q4NzYwMzEwMzNiZTExYzI1Y2E2Yjg2MGViNjRjMGE2YjcwNTEzY2ZkMDgxNTYwNTUxZWJjMzJhYjUwY2JiOTBiNjk3YWExMWRiNWM0MDljOTJhN2M2ODBkZDY5MjQ4MTVhYmUyZGUwYjFiODE1YmFhMTE0MjY2NDRiZDI5NDI=", + "testFrameworks": [], + "enableTranslation": false, + "skipClient": true, + "skipUserManagement": true + } +} \ No newline at end of file From 71631926ec5ad66800e70fd422f7d0855304daff Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Fri, 21 Jun 2019 10:35:36 +0200 Subject: [PATCH 18/22] clean previously generated code before regenerating client --- generators/openapi-client/files.js | 10 ++++++++-- package-lock.json | 1 - test/openapi-client.spec.js | 4 ++++ .../myapp/client/petstore/PetsApiClientOld.java | 0 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 test/templates/openapi-client/microservice-with-client/src/main/java/com/mycompany/myapp/client/petstore/PetsApiClientOld.java diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index 9037316e37dc..2941b6f29e58 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -22,6 +22,7 @@ const shelljs = require('shelljs'); const s = require('underscore.string'); const _ = require('lodash'); const chalk = require('chalk'); +const fs = require('fs-extra'); const jhipsterConstants = require('../generator-constants'); module.exports = { @@ -54,8 +55,13 @@ function writeFiles() { let JAVA_OPTS; let command; if (generatorName === 'spring') { + this.log(chalk.green(`\n\nGenerating java client code for client ${cliName} (${inputSpec})`)); const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; - this.log(chalk.green(`Generating java client code for ${cliName} (${inputSpec})`)); + const clientPackageLocation = path.resolve('src', 'main', 'java', ...cliPackage.split('.')); + if (fs.pathExistsSync(clientPackageLocation)) { + this.log(`cleanup generated java code for client ${cliName} in directory ${clientPackageLocation}`); + fs.removeSync(clientPackageLocation); + } JAVA_OPTS = ' -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java '; @@ -76,7 +82,7 @@ function writeFiles() { command = `java ${JAVA_OPTS} -jar ${jarPath} ${params}`; } - this.log(command); + this.log(`\n${command}`); const done = this.async(); shelljs.exec(command, { silent: this.silent }, (code, msg, err) => { diff --git a/package-lock.json b/package-lock.json index 7a6ed8f7f73f..612963251dfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2318,7 +2318,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", diff --git a/test/openapi-client.spec.js b/test/openapi-client.spec.js index 812840e0caec..123bfd9f2c6c 100644 --- a/test/openapi-client.spec.js +++ b/test/openapi-client.spec.js @@ -64,6 +64,10 @@ describe('JHipster OpenAPI Client Sub Generator', () => { it('regenerates java client files', () => { assert.file(expectedFiles.petstoreClientFiles); }); + + it('has removed old client file', () => { + assert.noFile(`${basePackage}/client/petstore/api/PetsApiClientOld.java`); + }); }); describe('Spring: microservice regenerate no clients ', () => { diff --git a/test/templates/openapi-client/microservice-with-client/src/main/java/com/mycompany/myapp/client/petstore/PetsApiClientOld.java b/test/templates/openapi-client/microservice-with-client/src/main/java/com/mycompany/myapp/client/petstore/PetsApiClientOld.java new file mode 100644 index 000000000000..e69de29bb2d1 From bfb125687ed2269b944d14881a51e5ddd9197ee7 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Tue, 25 Jun 2019 18:09:48 +0200 Subject: [PATCH 19/22] fix file path validation for custom endpoint --- generators/openapi-client/prompts.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/generators/openapi-client/prompts.js b/generators/openapi-client/prompts.js index a5acbb024caf..0bb783d6bf47 100644 --- a/generators/openapi-client/prompts.js +++ b/generators/openapi-client/prompts.js @@ -137,13 +137,16 @@ function askActionType() { store: true, validate: input => { try { - request('GET', `${input}`, { - // headers: { Accept: 'application/json, text/javascript;' } - }); - + if (/^((http|https):\/\/)/.test(input)) { + request('GET', `${input}`, { + // headers: { Accept: 'application/json, text/javascript;' } + }); + } else if (!shelljs.test('-f', input)) { + return `file '${input}' not found`; + } return true; } catch (err) { - return `Cannot read from ${input}`; + return `Cannot read from ${input}. ${err.message}`; } } } From f96ecbb5479aae3592385f6ff17e5a7ea62cdeda Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Tue, 25 Jun 2019 18:11:52 +0200 Subject: [PATCH 20/22] fix how the swagger-resources endpoint is built --- generators/openapi-client/prompts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/openapi-client/prompts.js b/generators/openapi-client/prompts.js index 0bb783d6bf47..ec4c4409dd55 100644 --- a/generators/openapi-client/prompts.js +++ b/generators/openapi-client/prompts.js @@ -29,14 +29,14 @@ module.exports = { function fetchSwaggerResources(input) { const availableDocs = []; - const swaggerResources = request('GET', `${input}/swagger-resources`, { + const baseUrl = input.replace(/\/$/, ''); + const swaggerResources = request('GET', `${baseUrl}/swagger-resources`, { // This header is needed to use the custom /swagger-resources controller // and not the default one that has only the gateway's swagger resource headers: { Accept: 'application/json, text/javascript;' } }); JSON.parse(swaggerResources.getBody()).forEach(swaggerResource => { - const baseUrl = input.replace(/\/$/, ''); const specPath = swaggerResource.location.replace(/^\/+/g, ''); availableDocs.push({ value: { url: `${baseUrl}/${specPath}`, name: swaggerResource.name }, From 2cc667270da64a49e350f2f38ae9507d0b271bcf Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Sun, 1 Sep 2019 11:54:46 +0200 Subject: [PATCH 21/22] use shelljs and lodash instead of fs-extra and underscore.string --- generators/openapi-client/files.js | 12 +++++------- package.json | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/generators/openapi-client/files.js b/generators/openapi-client/files.js index 2941b6f29e58..3ae5fdc687cd 100644 --- a/generators/openapi-client/files.js +++ b/generators/openapi-client/files.js @@ -19,10 +19,8 @@ const path = require('path'); const shelljs = require('shelljs'); -const s = require('underscore.string'); const _ = require('lodash'); const chalk = require('chalk'); -const fs = require('fs-extra'); const jhipsterConstants = require('../generator-constants'); module.exports = { @@ -56,11 +54,11 @@ function writeFiles() { let command; if (generatorName === 'spring') { this.log(chalk.green(`\n\nGenerating java client code for client ${cliName} (${inputSpec})`)); - const cliPackage = `${this.packageName}.client.${s.underscored(cliName)}`; + const cliPackage = `${this.packageName}.client.${_.snakeCase(cliName)}`; const clientPackageLocation = path.resolve('src', 'main', 'java', ...cliPackage.split('.')); - if (fs.pathExistsSync(clientPackageLocation)) { + if (shelljs.test('-d', clientPackageLocation)) { this.log(`cleanup generated java code for client ${cliName} in directory ${clientPackageLocation}`); - fs.removeSync(clientPackageLocation); + shelljs.rm('-rf', clientPackageLocation); } JAVA_OPTS = ' -Dmodels -Dapis -DsupportingFiles=ApiKeyRequestInterceptor.java,ClientConfiguration.java '; @@ -69,12 +67,12 @@ function writeFiles() { ' generate -g spring ' + ` -t ${path.resolve(__dirname, 'templates/swagger-codegen/libraries/spring-cloud')} ` + ' --library spring-cloud ' + - ` -i ${inputSpec} --artifact-id ${s.camelize(cliName)} --api-package ${cliPackage}.api` + + ` -i ${inputSpec} --artifact-id ${_.camelCase(cliName)} --api-package ${cliPackage}.api` + ` --model-package ${cliPackage}.model` + ' --type-mappings DateTime=OffsetDateTime,Date=LocalDate ' + ' --import-mappings OffsetDateTime=java.time.OffsetDateTime,LocalDate=java.time.LocalDate' + ` -DdateLibrary=custom,basePackage=${this.packageName}.client,configPackage=${cliPackage},` + - `title=${s.camelize(cliName)}`; + `title=${_.camelCase(cliName)}`; if (this.clientsToGenerate[cliName].useServiceDiscovery) { params += ' --additional-properties ribbon=true'; diff --git a/package.json b/package.json index 829e74e31cf9..ba1d6092675b 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "through2": "3.0.1", "uuid": "3.3.2", "sync-request": "6.0.0", - "underscore.string": "3.3.4", "yeoman-environment": "2.3.4", "yeoman-generator": "3.2.0", "yo": "3.1.0" From 374f57865086b9f81757c5fda3a1859bcc67eb09 Mon Sep 17 00:00:00 2001 From: Enrico Costanzi Date: Mon, 2 Sep 2019 08:35:48 +0200 Subject: [PATCH 22/22] update package-lock.json --- package-lock.json | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/package-lock.json b/package-lock.json index 612963251dfb..e101cc34b95d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,12 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, + "@openapitools/openapi-generator-cli": { + "version": "0.0.14-4.0.2", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-0.0.14-4.0.2.tgz", + "integrity": "sha512-x4gKaGNgG5Eqt43B4V814Bp0zUBT5h9/K0Tz4oTi5wGy+5sqU5/gElKys0jmM6wvEio2OXJm728BIFuKPuFi5w==", + "dev": true + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -85,6 +91,32 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", + "requires": { + "@types/node": "*" + } + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "10.14.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.17.tgz", + "integrity": "sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ==" + }, + "@types/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-Jugo5V/1bS0fRhy2z8+cUAHEyWOATaz4rbyLVvcFs7+dXp5HfwpEwzF1Q11bB10ApUqHf+yTauxI0UXQDwGrbA==" + }, "acorn": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", @@ -289,6 +321,11 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2318,6 +2355,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -2417,6 +2455,11 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -2732,11 +2775,30 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "requires": { + "@types/node": "^10.0.3" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -4603,6 +4665,11 @@ "callsites": "^3.0.0" } }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=" + }, "parse-gitignore": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-1.0.1.tgz", @@ -4836,6 +4903,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.3.tgz", + "integrity": "sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw==", + "requires": { + "asap": "~2.0.6" + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -5828,6 +5903,24 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" }, + "sync-request": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.0.0.tgz", + "integrity": "sha512-jGNIAlCi9iU4X3Dm4oQnNQshDD3h0/1A7r79LyqjbjUnj69sX6mShAXlhRXgImsfVKtTcnra1jfzabdZvp+Lmw==", + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "requires": { + "get-port": "^3.1.0" + } + }, "syntax-error": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", @@ -6144,6 +6237,31 @@ "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.4.0.tgz", "integrity": "sha512-qftQXnX1DzpSV8EddtHIT0eDDEiBF8ywhFYR2lI9xrGtxqKN+CvLXhACeCIGbCpQfxxERbrkZEFb8cZcDKbVZA==" }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.53", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.53.tgz", + "integrity": "sha512-aOmXdv1a1/vYUn1OT1CED8ftbkmmYbKhKGSyMDeJiidLvKRKvZUQOdXwG/wcNY7T1Qb0XTlVdiYjIq00U7pLrQ==" + } + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",