From 3b90994987e02b712a37099c2c679bde78112fc8 Mon Sep 17 00:00:00 2001 From: Dmitrii Date: Tue, 12 Sep 2023 16:18:26 +0200 Subject: [PATCH 1/2] Extract OpeanAPI codegen to a package --- .github/CODEOWNERS | 1 + package.json | 5 +- packages/kbn-openapi-generator/README.md | 201 ++++++++++++++++++ packages/kbn-openapi-generator/image.png | Bin 0 -> 104880 bytes packages/kbn-openapi-generator/index.ts | 10 + packages/kbn-openapi-generator/jest.config.js | 13 ++ packages/kbn-openapi-generator/kibana.jsonc | 6 + packages/kbn-openapi-generator/package.json | 10 + packages/kbn-openapi-generator/src/cli.ts | 52 +++++ .../src}/lib/fix_eslint.ts | 11 +- .../src}/lib/format_output.ts | 5 +- .../src/lib/get_generated_file_path.ts | 11 + .../src}/lib/remove_gen_artifacts.ts | 5 +- .../src/openapi_generator.ts | 77 +++++++ .../src/parser/get_generation_context.ts | 34 +++ .../parser/lib}/get_api_operations_list.ts | 24 ++- .../src/parser/lib}/get_components.ts | 8 +- .../src/parser/lib}/get_imports_map.ts | 28 +-- .../src/parser/lib/normalize_schema.ts | 36 ++++ .../src/parser/lib/traverse_object.ts | 31 +++ .../src/parser}/openapi_types.ts | 30 ++- .../src}/template_service/register_helpers.ts | 17 +- .../template_service/register_templates.ts | 5 +- .../src}/template_service/template_service.ts | 21 +- .../templates/disclaimer.handlebars | 2 +- .../templates/zod_operation_schema.handlebars | 18 +- .../templates/zod_query_item.handlebars | 51 +++++ .../templates/zod_schema_item.handlebars | 28 ++- packages/kbn-openapi-generator/tsconfig.json | 13 ++ scripts/generate_openapi.js | 10 + tsconfig.base.json | 2 + .../model/warning_schema.gen.ts | 2 +- ...lt_rules_and_timelines_status_route.gen.ts | 16 +- .../api/endpoint/actions/audit_log.gen.ts | 2 +- .../api/endpoint/actions/details.gen.ts | 2 +- .../api/endpoint/actions/execute.gen.ts | 2 +- .../api/endpoint/actions/file_download.gen.ts | 2 +- .../api/endpoint/actions/file_info.gen.ts | 2 +- .../api/endpoint/actions/file_upload.gen.ts | 2 +- .../api/endpoint/actions/get_file.gen.ts | 2 +- .../common/api/endpoint/actions/list.gen.ts | 4 +- .../endpoint/metadata/list_metadata.gen.ts | 6 +- .../api/endpoint/model/schema/common.gen.ts | 16 +- .../common/api/endpoint/policy/policy.gen.ts | 2 +- .../suggestions/get_suggestions.gen.ts | 2 +- .../scripts/openapi/generate.js | 11 +- .../scripts/openapi/openapi_generator.ts | 77 ------- .../plugins/security_solution/tsconfig.json | 4 +- yarn.lock | 4 + 49 files changed, 715 insertions(+), 208 deletions(-) create mode 100644 packages/kbn-openapi-generator/README.md create mode 100644 packages/kbn-openapi-generator/image.png create mode 100644 packages/kbn-openapi-generator/index.ts create mode 100644 packages/kbn-openapi-generator/jest.config.js create mode 100644 packages/kbn-openapi-generator/kibana.jsonc create mode 100644 packages/kbn-openapi-generator/package.json create mode 100644 packages/kbn-openapi-generator/src/cli.ts rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/fix_eslint.ts (62%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/format_output.ts (61%) create mode 100644 packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/lib/remove_gen_artifacts.ts (75%) create mode 100644 packages/kbn-openapi-generator/src/openapi_generator.ts create mode 100644 packages/kbn-openapi-generator/src/parser/get_generation_context.ts rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_api_operations_list.ts (87%) rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_components.ts (59%) rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser/lib}/get_imports_map.ts (76%) create mode 100644 packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts create mode 100644 packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts rename {x-pack/plugins/security_solution/scripts/openapi/parsers => packages/kbn-openapi-generator/src/parser}/openapi_types.ts (62%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/register_helpers.ts (75%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/register_templates.ts (83%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/template_service.ts (60%) rename {x-pack/plugins/security_solution/scripts/openapi => packages/kbn-openapi-generator/src}/template_service/templates/disclaimer.handlebars (81%) rename x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars => packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars (75%) create mode 100644 packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars rename x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars => packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars (79%) create mode 100644 packages/kbn-openapi-generator/tsconfig.json create mode 100644 scripts/generate_openapi.js delete mode 100644 x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b6a1c3e86c3e75..647de24ad4920d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -539,6 +539,7 @@ x-pack/plugins/observability @elastic/actionable-observability x-pack/plugins/observability_shared @elastic/observability-ui x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security test/common/plugins/otel_metrics @elastic/infra-monitoring-ui +packages/kbn-openapi-generator @elastic/security-detection-engine packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations packages/kbn-osquery-io-ts-types @elastic/security-asset-management diff --git a/package.json b/package.json index eaafb301c5dfc4..54123db6f2eb7e 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", "build": { + "date": "2023-05-15T23:12:09+0000", "number": 8467, - "sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9", - "date": "2023-05-15T23:12:09+0000" + "sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9" }, "homepage": "https://www.elastic.co/products/kibana", "bugs": { @@ -1205,6 +1205,7 @@ "@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config", "@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli", "@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config", + "@kbn/openapi-generator": "link:packages/kbn-openapi-generator", "@kbn/optimizer": "link:packages/kbn-optimizer", "@kbn/optimizer-webpack-helpers": "link:packages/kbn-optimizer-webpack-helpers", "@kbn/peggy": "link:packages/kbn-peggy", diff --git a/packages/kbn-openapi-generator/README.md b/packages/kbn-openapi-generator/README.md new file mode 100644 index 00000000000000..fc75a76827934a --- /dev/null +++ b/packages/kbn-openapi-generator/README.md @@ -0,0 +1,201 @@ +# OpenAPI Code Generator for Kibana + +This code generator could be used to generate runtime types, documentation, server stub implementations, clients, and much more given OpenAPI specification. + +## Getting started + +To start with code generation you should have OpenAPI specification describing your API endpoint request and response schemas along with common types used in your API. The code generation script supports OpenAPI 3.1.0, refer to https://swagger.io/specification/ for more details. + +OpenAPI specification should be in YAML format and have `.schema.yaml` extension. Here's a simple example of OpenAPI specification: + +```yaml +openapi: 3.0.0 +info: + title: Install Prebuilt Rules API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules/prepackaged: + put: + operationId: InstallPrebuiltRules + x-codegen-enabled: true + summary: Installs all Elastic prebuilt rules and timelines + tags: + - Prebuilt Rules API + responses: + 200: + description: Indicates a successful call + content: + application/json: + schema: + type: object + properties: + rules_installed: + type: integer + description: The number of rules installed + minimum: 0 + rules_updated: + type: integer + description: The number of rules updated + minimum: 0 + timelines_installed: + type: integer + description: The number of timelines installed + minimum: 0 + timelines_updated: + type: integer + description: The number of timelines updated + minimum: 0 + required: + - rules_installed + - rules_updated + - timelines_installed + - timelines_updated +``` + +Put it anywhere in your plugin, the code generation script will traverse the whole plugin directory and find all `.schema.yaml` files. + +Then to generate code run the following command: + +```bash +node scripts/generate_openapi --rootDir ./x-pack/plugins/security_solution +``` + +![Generator command output](image.png) + +By default it uses the `zod_operation_schema` template which produces runtime types for request and response schemas described in OpenAPI specification. The generated code will be placed adjacent to the `.schema.yaml` file and will have `.gen.ts` extension. + +Example of generated code: + +```ts +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type InstallPrebuiltRulesResponse = z.infer; +export const InstallPrebuiltRulesResponse = z.object({ + /** + * The number of rules installed + */ + rules_installed: z.number().int().min(0), + /** + * The number of rules updated + */ + rules_updated: z.number().int().min(0), + /** + * The number of timelines installed + */ + timelines_installed: z.number().int().min(0), + /** + * The number of timelines updated + */ + timelines_updated: z.number().int().min(0), +}); +``` +## Programmatic API + +Alternatively, you can use the code generator programmatically. You can create a script file and run it with `node` command. This could be useful if you want to set up code generation in your CI pipeline. Here's an example of such script: + +```ts +require('../../../../../src/setup_node_env'); +const { generate } = require('@kbn/openapi-generator'); +const { resolve } = require('path'); + +const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); + +generate({ + rootDir: SECURITY_SOLUTION_ROOT, // Path to the plugin root directory + sourceGlob: './**/*.schema.yaml', // Glob pattern to find OpenAPI specification files + templateName: 'zod_operation_schema', // Name of the template to use +}); +``` + +## CI integration + +To make sure that generated code is always in sync with its OpenAPI specification it is recommended to add a command to your CI pipeline that will run code generation on every pull request and commit the changes if there are any. + +First, create a script that will run code generation and commit the changes. See `.buildkite/scripts/steps/code_generation/security_solution_codegen.sh` for an example: + +```bash +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh + +echo --- Security Solution OpenAPI Code Generation + +(cd x-pack/plugins/security_solution && yarn openapi:generate) +check_for_changed_files "yarn openapi:generate" true +``` + +This scripts sets up the minimal environment required fro code generation and runs the code generation script. Then it checks if there are any changes and commits them if there are any using the `check_for_changed_files` function. + +Then add the code generation script to your plugin build pipeline. Open your plugin build pipeline, for example `.buildkite/pipelines/pull_request/security_solution.yml`, and add the following command to the steps list adjusting the path to your code generation script: + +```yaml + - command: .buildkite/scripts/steps/code_generation/security_solution_codegen.sh + label: 'Security Solution OpenAPI codegen' + agents: + queue: n2-2-spot + timeout_in_minutes: 60 + parallelism: 1 +``` + +Now on every pull request the code generation script will run and commit the changes if there are any. + +## OpenAPI Schema + +The code generator supports the OpenAPI definitions described in the request, response, and component sections of the document. + +For every API operation (GET, POST, etc) it is required to specify the `operationId` field. This field is used to generate the name of the generated types. For example, if the `operationId` is `InstallPrebuiltRules` then the generated types will be named `InstallPrebuiltRulesResponse` and `InstallPrebuiltRulesRequest`. If the `operationId` is not specified then the code generation will throw an error. + +The `x-codegen-enabled` field is used to enable or disable code generation for the operation. If it is not specified then code generation is disabled by default. This field could be also used to disable code generation of common components described in the `components` section of the OpenAPI specification. + +Keep in mind that disabling code generation for common components that are referenced by external OpenAPI specifications could lead to errors during code generation. + +### Schema files organization + +It is recommended to limit the number of operations and components described in a single OpenAPI specification file. Having one HTTP operation in a single file will make it easier to maintain and will keep the generated artifacts granular for ease of reuse and better tree shaking. You can have as many OpenAPI specification files as you want. + +### Common components + +It is common to have shared types that are used in multiple API operations. To avoid code duplication you can define common components in the `components` section of the OpenAPI specification and put them in a separate file. Then you can reference these components in the `parameters` and `responses` sections of the API operations. + +Here's an example of the schema that references common components: + +```yaml +openapi: 3.0.0 +info: + title: Delete Rule API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules: + delete: + operationId: DeleteRule + description: Deletes a single rule using the `rule_id` or `id` field. + parameters: + - name: id + in: query + required: false + description: The rule's `id` value. + schema: + $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleSignatureId' + - name: rule_id + in: query + required: false + description: The rule's `rule_id` value. + schema: + $ref: '../../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleObjectId' + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../../../model/rule_schema/rule_schemas.schema.yaml#/components/schemas/RuleResponse' +``` diff --git a/packages/kbn-openapi-generator/image.png b/packages/kbn-openapi-generator/image.png new file mode 100644 index 0000000000000000000000000000000000000000..e8710d78b80b6d5033271d5fdcb5446a9cade4a6 GIT binary patch literal 104880 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 F{|k19$MFCF literal 0 HcmV?d00001 diff --git a/packages/kbn-openapi-generator/index.ts b/packages/kbn-openapi-generator/index.ts new file mode 100644 index 00000000000000..eeaad5343dc9f3 --- /dev/null +++ b/packages/kbn-openapi-generator/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/openapi_generator'; +export * from './src/cli'; diff --git a/packages/kbn-openapi-generator/jest.config.js b/packages/kbn-openapi-generator/jest.config.js new file mode 100644 index 00000000000000..6b5b1fce1c4d47 --- /dev/null +++ b/packages/kbn-openapi-generator/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-openapi-generator'], +}; diff --git a/packages/kbn-openapi-generator/kibana.jsonc b/packages/kbn-openapi-generator/kibana.jsonc new file mode 100644 index 00000000000000..b507d94ec022d6 --- /dev/null +++ b/packages/kbn-openapi-generator/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "devOnly": true, + "id": "@kbn/openapi-generator", + "owner": "@elastic/security-detection-engine", + "type": "shared-common" +} diff --git a/packages/kbn-openapi-generator/package.json b/packages/kbn-openapi-generator/package.json new file mode 100644 index 00000000000000..5847d729d025c9 --- /dev/null +++ b/packages/kbn-openapi-generator/package.json @@ -0,0 +1,10 @@ +{ + "bin": { + "openapi-generator": "./bin/openapi-generator.js" + }, + "description": "OpenAPI code generator for Kibana", + "license": "SSPL-1.0 OR Elastic License 2.0", + "name": "@kbn/openapi-generator", + "private": true, + "version": "1.0.0" +} diff --git a/packages/kbn-openapi-generator/src/cli.ts b/packages/kbn-openapi-generator/src/cli.ts new file mode 100644 index 00000000000000..722fc6123e899e --- /dev/null +++ b/packages/kbn-openapi-generator/src/cli.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import yargs from 'yargs/yargs'; +import { generate } from './openapi_generator'; +import { AVAILABLE_TEMPLATES } from './template_service/template_service'; + +export function runCli() { + yargs(process.argv.slice(2)) + .command( + '*', + 'Generate code artifacts from OpenAPI specifications', + (y) => + y + .option('rootDir', { + describe: 'Root directory to search for OpenAPI specs', + demandOption: true, + string: true, + }) + .option('sourceGlob', { + describe: 'Elasticsearch target', + default: './**/*.schema.yaml', + string: true, + }) + .option('templateName', { + describe: 'Template to use for code generation', + default: 'zod_operation_schema' as const, + choices: AVAILABLE_TEMPLATES, + }) + .showHelpOnFail(false), + (argv) => { + generate(argv).catch((err) => { + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); + }); + } + ) + .parse(); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts b/packages/kbn-openapi-generator/src/lib/fix_eslint.ts similarity index 62% rename from x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts rename to packages/kbn-openapi-generator/src/lib/fix_eslint.ts index 23d8bf540f7317..c205dbcebf1644 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/fix_eslint.ts +++ b/packages/kbn-openapi-generator/src/lib/fix_eslint.ts @@ -1,19 +1,18 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import execa from 'execa'; -import { resolve } from 'path'; - -const KIBANA_ROOT = resolve(__dirname, '../../../../../../'); +import { REPO_ROOT } from '@kbn/repo-info'; export async function fixEslint(path: string) { await execa('npx', ['eslint', '--fix', path], { // Need to run eslint from the Kibana root directory, otherwise it will not // be able to pick up the right config - cwd: KIBANA_ROOT, + cwd: REPO_ROOT, }); } diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts b/packages/kbn-openapi-generator/src/lib/format_output.ts similarity index 61% rename from x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts rename to packages/kbn-openapi-generator/src/lib/format_output.ts index 6c374aa1f06d25..7b50b0732009bb 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/format_output.ts +++ b/packages/kbn-openapi-generator/src/lib/format_output.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import execa from 'execa'; diff --git a/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts b/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts new file mode 100644 index 00000000000000..4139d3610d8b65 --- /dev/null +++ b/packages/kbn-openapi-generator/src/lib/get_generated_file_path.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export function getGeneratedFilePath(sourcePath: string) { + return sourcePath.replace(/\..+$/, '.gen.ts'); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts b/packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts rename to packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts index 3cbf421b8c94b3..45933864faf8f0 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/lib/remove_gen_artifacts.ts +++ b/packages/kbn-openapi-generator/src/lib/remove_gen_artifacts.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import fs from 'fs/promises'; diff --git a/packages/kbn-openapi-generator/src/openapi_generator.ts b/packages/kbn-openapi-generator/src/openapi_generator.ts new file mode 100644 index 00000000000000..539994a2581264 --- /dev/null +++ b/packages/kbn-openapi-generator/src/openapi_generator.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable no-console */ + +import SwaggerParser from '@apidevtools/swagger-parser'; +import chalk from 'chalk'; +import fs from 'fs/promises'; +import globby from 'globby'; +import { resolve } from 'path'; +import { fixEslint } from './lib/fix_eslint'; +import { formatOutput } from './lib/format_output'; +import { getGeneratedFilePath } from './lib/get_generated_file_path'; +import { removeGenArtifacts } from './lib/remove_gen_artifacts'; +import { getGenerationContext } from './parser/get_generation_context'; +import type { OpenApiDocument } from './parser/openapi_types'; +import { initTemplateService, TemplateName } from './template_service/template_service'; + +export interface GeneratorConfig { + rootDir: string; + sourceGlob: string; + templateName: TemplateName; +} + +export const generate = async (config: GeneratorConfig) => { + const { rootDir, sourceGlob, templateName } = config; + + console.log(chalk.bold(`Generating API route schemas`)); + console.log(chalk.bold(`Working directory: ${chalk.underline(rootDir)}`)); + + console.log(`๐Ÿ‘€ Searching for source files`); + const sourceFilesGlob = resolve(rootDir, sourceGlob); + const schemaPaths = await globby([sourceFilesGlob]); + + console.log(`๐Ÿ•ต๏ธโ€โ™€๏ธ Found ${schemaPaths.length} schemas, parsing`); + const parsedSources = await Promise.all( + schemaPaths.map(async (sourcePath) => { + const parsedSchema = (await SwaggerParser.parse(sourcePath)) as OpenApiDocument; + return { sourcePath, parsedSchema }; + }) + ); + + console.log(`๐Ÿงน Cleaning up any previously generated artifacts`); + await removeGenArtifacts(rootDir); + + console.log(`๐Ÿช„ Generating new artifacts`); + const TemplateService = await initTemplateService(); + await Promise.all( + parsedSources.map(async ({ sourcePath, parsedSchema }) => { + const generationContext = getGenerationContext(parsedSchema); + + // If there are no operations or components to generate, skip this file + const shouldGenerate = + generationContext.operations.length > 0 || generationContext.components !== undefined; + if (!shouldGenerate) { + return; + } + + const result = TemplateService.compileTemplate(templateName, generationContext); + + // Write the generation result to disk + await fs.writeFile(getGeneratedFilePath(sourcePath), result); + }) + ); + + // Format the output folder using prettier as the generator produces + // unformatted code and fix any eslint errors + console.log(`๐Ÿ’… Formatting output`); + const generatedArtifactsGlob = resolve(rootDir, './**/*.gen.ts'); + await formatOutput(generatedArtifactsGlob); + await fixEslint(generatedArtifactsGlob); +}; diff --git a/packages/kbn-openapi-generator/src/parser/get_generation_context.ts b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts new file mode 100644 index 00000000000000..14a3b8eb2e178e --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OpenAPIV3 } from 'openapi-types'; +import { getApiOperationsList } from './lib/get_api_operations_list'; +import { getComponents } from './lib/get_components'; +import { getImportsMap, ImportsMap } from './lib/get_imports_map'; +import { normalizeSchema } from './lib/normalize_schema'; +import { NormalizedOperation, OpenApiDocument } from './openapi_types'; + +export interface GenerationContext { + components: OpenAPIV3.ComponentsObject | undefined; + operations: NormalizedOperation[]; + imports: ImportsMap; +} + +export function getGenerationContext(document: OpenApiDocument): GenerationContext { + const normalizedDocument = normalizeSchema(document); + + const components = getComponents(normalizedDocument); + const operations = getApiOperationsList(normalizedDocument); + const imports = getImportsMap(normalizedDocument); + + return { + components, + operations, + imports, + }; +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts b/packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts similarity index 87% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts index c9d9a75c078541..ac63820555419d 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_api_operations_list.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_api_operations_list.ts @@ -1,12 +1,18 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { OpenAPIV3 } from 'openapi-types'; -import type { NormalizedOperation, ObjectSchema, OpenApiDocument } from './openapi_types'; +import type { + NormalizedOperation, + NormalizedSchemaItem, + ObjectSchema, + OpenApiDocument, +} from '../openapi_types'; const HTTP_METHODS = Object.values(OpenAPIV3.HttpMethods); @@ -61,14 +67,12 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO `Cannot generate response for ${method} ${path}: $ref in response is not supported` ); } - const response = operation.responses?.['200']?.content?.['application/json']?.schema; if (operation.requestBody && '$ref' in operation.requestBody) { throw new Error( `Cannot generate request for ${method} ${path}: $ref in request body is not supported` ); } - const requestBody = operation.requestBody?.content?.['application/json']?.schema; const { operationId, description, tags, deprecated } = operation; @@ -78,7 +82,13 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO throw new Error(`Missing operationId for ${method} ${path}`); } - return { + const response = operation.responses?.['200']?.content?.['application/json']?.schema as + | NormalizedSchemaItem + | undefined; + const requestBody = operation.requestBody?.content?.['application/json']?.schema as + | NormalizedSchemaItem + | undefined; + const normalizedOperation: NormalizedOperation = { path, method, operationId, @@ -90,6 +100,8 @@ export function getApiOperationsList(parsedSchema: OpenApiDocument): NormalizedO requestBody, response, }; + + return normalizedOperation; }); } ); diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts b/packages/kbn-openapi-generator/src/parser/lib/get_components.ts similarity index 59% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_components.ts index 5b3fef72905c0e..6e98793de1afb6 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_components.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_components.ts @@ -1,15 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import type { OpenApiDocument } from './openapi_types'; +import type { OpenApiDocument } from '../openapi_types'; export function getComponents(parsedSchema: OpenApiDocument) { if (parsedSchema.components?.['x-codegen-enabled'] === false) { return undefined; } + return parsedSchema.components; } diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts similarity index 76% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts rename to packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts index 8e068b61ba0344..c2259052e0cb6f 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/get_imports_map.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts @@ -1,12 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { uniq } from 'lodash'; -import type { OpenApiDocument } from './openapi_types'; +import type { OpenApiDocument } from '../openapi_types'; +import { traverseObject } from './traverse_object'; export interface ImportsMap { [importPath: string]: string[]; @@ -55,23 +57,11 @@ const hasRef = (obj: unknown): obj is { $ref: string } => { function findRefs(obj: unknown): string[] { const refs: string[] = []; - function search(element: unknown) { - if (typeof element === 'object' && element !== null) { - if (hasRef(element)) { - refs.push(element.$ref); - } - - Object.values(element).forEach((value) => { - if (Array.isArray(value)) { - value.forEach(search); - } else { - search(value); - } - }); + traverseObject(obj, (element) => { + if (hasRef(element)) { + refs.push(element.$ref); } - } - - search(obj); + }); return refs; } diff --git a/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts new file mode 100644 index 00000000000000..0ea2062e369c3a --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OpenAPIV3 } from 'openapi-types'; +import { NormalizedReferenceObject } from '../openapi_types'; +import { traverseObject } from './traverse_object'; + +/** + * Check if an object has a $ref property + * + * @param obj Any object + * @returns True if the object has a $ref property + */ +const hasRef = (obj: unknown): obj is NormalizedReferenceObject => { + return typeof obj === 'object' && obj !== null && '$ref' in obj; +}; + +export function normalizeSchema(schema: OpenAPIV3.Document) { + traverseObject(schema, (element) => { + if (hasRef(element)) { + const referenceName = element.$ref.split('/').pop(); + if (!referenceName) { + throw new Error(`Cannot parse reference name: ${element.$ref}`); + } + + element.referenceName = referenceName; + } + }); + + return schema; +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts b/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts new file mode 100644 index 00000000000000..2049465e0753b7 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * A generic function to traverse an object or array of objects recursively + * + * @param obj The object to traverse + * @param onVisit A function that will be called for each traversed node in the object + */ +export function traverseObject(obj: unknown, onVisit: (element: object) => void) { + function search(element: unknown) { + if (typeof element === 'object' && element !== null) { + onVisit(element); + + Object.values(element).forEach((value) => { + if (Array.isArray(value)) { + value.forEach(search); + } else { + search(value); + } + }); + } + } + + search(obj); +} diff --git a/x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts b/packages/kbn-openapi-generator/src/parser/openapi_types.ts similarity index 62% rename from x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts rename to packages/kbn-openapi-generator/src/parser/openapi_types.ts index 2449f34fa4b768..c8b1e4f7153456 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/parsers/openapi_types.ts +++ b/packages/kbn-openapi-generator/src/parser/openapi_types.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type { OpenAPIV3 } from 'openapi-types'; @@ -27,6 +28,21 @@ declare module 'openapi-types' { } } +export type NormalizedReferenceObject = OpenAPIV3.ReferenceObject & { + referenceName: string; +}; + +export interface UnknownType { + type: 'unknown'; +} + +export type NormalizedSchemaObject = + | OpenAPIV3.ArraySchemaObject + | OpenAPIV3.NonArraySchemaObject + | UnknownType; + +export type NormalizedSchemaItem = OpenAPIV3.SchemaObject | NormalizedReferenceObject; + /** * OpenAPI types do not have a dedicated type for objects, so we need to create * to use for path and query parameters @@ -36,7 +52,7 @@ export interface ObjectSchema { required: string[]; description?: string; properties: { - [name: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; + [name: string]: NormalizedSchemaItem; }; } @@ -50,8 +66,8 @@ export interface NormalizedOperation { description?: string; tags?: string[]; deprecated?: boolean; - requestParams?: ObjectSchema; - requestQuery?: ObjectSchema; - requestBody?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; - response?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject; + requestParams?: NormalizedSchemaItem; + requestQuery?: NormalizedSchemaItem; + requestBody?: NormalizedSchemaItem; + response?: NormalizedSchemaItem; } diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts rename to packages/kbn-openapi-generator/src/template_service/register_helpers.ts index b3bb02f7743c89..1431dafcdfba9f 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_helpers.ts +++ b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type Handlebars from '@kbn/handlebars'; @@ -13,9 +14,6 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) { const values = args.slice(0, -1) as unknown[]; return values.join(''); }); - handlebarsInstance.registerHelper('parseRef', (refName: string) => { - return refName.split('/').pop(); - }); handlebarsInstance.registerHelper('snakeCase', snakeCase); handlebarsInstance.registerHelper('camelCase', camelCase); handlebarsInstance.registerHelper('toJSON', (value: unknown) => { @@ -43,13 +41,4 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) { handlebarsInstance.registerHelper('isUnknown', (val: object) => { return !('type' in val || '$ref' in val || 'anyOf' in val || 'oneOf' in val || 'allOf' in val); }); - handlebarsInstance.registerHelper('isEmpty', (val) => { - if (Array.isArray(val)) { - return val.length === 0; - } - if (typeof val === 'object') { - return Object.keys(val).length === 0; - } - return val === undefined || val === null || val === ''; - }); } diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts b/packages/kbn-openapi-generator/src/template_service/register_templates.ts similarity index 83% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts rename to packages/kbn-openapi-generator/src/template_service/register_templates.ts index fa39b52d99471b..b8f9711ecb1a66 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/register_templates.ts +++ b/packages/kbn-openapi-generator/src/template_service/register_templates.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import type Handlebars from '@kbn/handlebars'; diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts b/packages/kbn-openapi-generator/src/template_service/template_service.ts similarity index 60% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts rename to packages/kbn-openapi-generator/src/template_service/template_service.ts index becb02bb54ebe2..9980282bbfdc3c 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/template_service.ts +++ b/packages/kbn-openapi-generator/src/template_service/template_service.ts @@ -1,28 +1,23 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import Handlebars from 'handlebars'; -import type { OpenAPIV3 } from 'openapi-types'; import { resolve } from 'path'; -import type { ImportsMap } from '../parsers/get_imports_map'; -import type { NormalizedOperation } from '../parsers/openapi_types'; +import { GenerationContext } from '../parser/get_generation_context'; import { registerHelpers } from './register_helpers'; import { registerTemplates } from './register_templates'; -export interface TemplateContext { - importsMap: ImportsMap; - apiOperations: NormalizedOperation[]; - components: OpenAPIV3.ComponentsObject | undefined; -} +export const AVAILABLE_TEMPLATES = ['zod_operation_schema'] as const; -export type TemplateName = 'schemas'; +export type TemplateName = typeof AVAILABLE_TEMPLATES[number]; export interface ITemplateService { - compileTemplate: (templateName: TemplateName, context: TemplateContext) => string; + compileTemplate: (templateName: TemplateName, context: GenerationContext) => string; } /** @@ -36,7 +31,7 @@ export const initTemplateService = async (): Promise => { const templates = await registerTemplates(resolve(__dirname, './templates'), handlebars); return { - compileTemplate: (templateName: TemplateName, context: TemplateContext) => { + compileTemplate: (templateName: TemplateName, context: GenerationContext) => { return handlebars.compile(templates[templateName])(context); }, }; diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars similarity index 81% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars index 4be0a93d1b79e6..403a5b54f09b37 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/disclaimer.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/disclaimer.handlebars @@ -1,5 +1,5 @@ /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ \ No newline at end of file diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars similarity index 75% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars index a6df5d96b124fe..0b129b3aa13ed9 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schemas.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars @@ -9,7 +9,7 @@ import { z } from "zod"; {{> disclaimer}} -{{#each importsMap}} +{{#each imports}} import { {{#each this}}{{.}},{{/each}} } from "{{@key}}" @@ -22,11 +22,15 @@ import { */ {{/description}} export type {{@key}} = z.infer; -export const {{@key}} = {{> schema_item}}; +export const {{@key}} = {{> zod_schema_item}}; +{{#if enum}} +export const {{@key}}Enum = {{@key}}.enum; +export type {{@key}}Enum = typeof {{@key}}.enum; +{{/if}} {{/each}} -{{#each apiOperations}} +{{#each operations}} {{#if requestQuery}} {{#if requestQuery.description}} /** @@ -34,7 +38,7 @@ export const {{@key}} = {{> schema_item}}; */ {{/if}} export type {{operationId}}RequestQuery = z.infer; -export const {{operationId}}RequestQuery = {{> schema_item requestQuery }}; +export const {{operationId}}RequestQuery = {{> zod_query_item requestQuery }}; export type {{operationId}}RequestQueryInput = z.input; {{/if}} @@ -45,7 +49,7 @@ export type {{operationId}}RequestQueryInput = z.input; -export const {{operationId}}RequestParams = {{> schema_item requestParams }}; +export const {{operationId}}RequestParams = {{> zod_schema_item requestParams }}; export type {{operationId}}RequestParamsInput = z.input; {{/if}} @@ -56,7 +60,7 @@ export type {{operationId}}RequestParamsInput = z.input; -export const {{operationId}}RequestBody = {{> schema_item requestBody }}; +export const {{operationId}}RequestBody = {{> zod_schema_item requestBody }}; export type {{operationId}}RequestBodyInput = z.input; {{/if}} @@ -67,6 +71,6 @@ export type {{operationId}}RequestBodyInput = z.input; -export const {{operationId}}Response = {{> schema_item response }}; +export const {{operationId}}Response = {{> zod_schema_item response }}; {{/if}} {{/each}} diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars new file mode 100644 index 00000000000000..0b718d941cbed2 --- /dev/null +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars @@ -0,0 +1,51 @@ +{{~#if $ref~}} + {{referenceName}} + {{~#if nullable}}.nullable(){{/if~}} + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} +{{~/if~}} + +{{~#if (eq type "object")}} + z.object({ + {{#each properties}} + {{#if description}} + /** + * {{{description}}} + */ + {{/if}} + {{@key}}: {{> zod_query_item requiredBool=(includes ../required @key)}}, + {{/each}} + }) +{{~/if~}} + +{{~#if (eq type "array")}} + z.preprocess( + (value: unknown) => (typeof value === "string") ? value === '' ? [] : value.split(",") : value, + z.array({{~> zod_schema_item items ~}}) + ) + {{~#if minItems}}.min({{minItems}}){{/if~}} + {{~#if maxItems}}.max({{maxItems}}){{/if~}} + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} +{{~/if~}} + +{{~#if (eq type "boolean")}} + z.preprocess( + (value: unknown) => (typeof value === "boolean") ? String(value) : value, + z.enum(["true", "false"]) + {{~#if (defined default)}}.default("{{{toJSON default}}}"){{/if~}} + .transform((value) => value === "true") + ) +{{~/if~}} + +{{~#if (eq type "string")}} +{{> zod_schema_item}} +{{~/if~}} + +{{~#if (eq type "integer")}} +{{> zod_schema_item}} +{{~/if~}} + +{{~#if (eq type "number")}} +{{> zod_schema_item}} +{{~/if~}} diff --git a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars similarity index 79% rename from x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars rename to packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars index 2d544a702ac004..dbf156b6a7b125 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/template_service/templates/schema_item.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars @@ -6,7 +6,7 @@ {{~/if~}} {{~#if $ref~}} - {{parseRef $ref}} + {{referenceName}} {{~#if nullable}}.nullable(){{/if~}} {{~#if (eq requiredBool false)}}.optional(){{/if~}} {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} @@ -15,9 +15,9 @@ {{~#if allOf~}} {{~#each allOf~}} {{~#if @first~}} - {{> schema_item }} + {{> zod_schema_item }} {{~else~}} - .and({{> schema_item }}) + .and({{> zod_schema_item }}) {{~/if~}} {{~/each~}} {{~/if~}} @@ -25,7 +25,7 @@ {{~#if anyOf~}} z.union([ {{~#each anyOf~}} - {{~> schema_item ~}}, + {{~> zod_schema_item ~}}, {{~/each~}} ]) {{~/if~}} @@ -33,7 +33,7 @@ {{~#if oneOf~}} z.union([ {{~#each oneOf~}} - {{~> schema_item ~}}, + {{~> zod_schema_item ~}}, {{~/each~}} ]) {{~/if~}} @@ -43,11 +43,7 @@ z.unknown() {{/if}} {{~#*inline "type_array"~}} - {{~#if x-preprocess}} - z.preprocess({{x-preprocess}}, z.array({{~> schema_item items ~}})) - {{else}} - z.array({{~> schema_item items ~}}) - {{~/if~}} + z.array({{~> zod_schema_item items ~}}) {{~#if minItems}}.min({{minItems}}){{/if~}} {{~#if maxItems}}.max({{maxItems}}){{/if~}} {{~/inline~}} @@ -58,11 +54,13 @@ z.unknown() {{~/inline~}} {{~#*inline "type_integer"~}} - {{~#if x-coerce}} - z.coerce.number() - {{~else~}} + z.number().int() + {{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}} + {{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}} +{{~/inline~}} + +{{~#*inline "type_number"~}} z.number() - {{~/if~}} {{~#if minimum includeZero=true}}.min({{minimum}}){{/if~}} {{~#if maximum includeZero=true}}.max({{maximum}}){{/if~}} {{~/inline~}} @@ -75,7 +73,7 @@ z.unknown() * {{{description}}} */ {{/if}} - {{@key}}: {{> schema_item requiredBool=(includes ../required @key)}}, + {{@key}}: {{> zod_schema_item requiredBool=(includes ../required @key)}}, {{/each}} }) {{#if (eq additionalProperties false)}}.strict(){{/if}} diff --git a/packages/kbn-openapi-generator/tsconfig.json b/packages/kbn-openapi-generator/tsconfig.json new file mode 100644 index 00000000000000..465b739262cf82 --- /dev/null +++ b/packages/kbn-openapi-generator/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"] + }, + "exclude": ["target/**/*"], + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"], + "kbn_references": [ + "@kbn/repo-info", + "@kbn/handlebars", + ] +} diff --git a/scripts/generate_openapi.js b/scripts/generate_openapi.js new file mode 100644 index 00000000000000..2dfae34bf46dd7 --- /dev/null +++ b/scripts/generate_openapi.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../src/setup_node_env'); +require('@kbn/openapi-generator').runCli(); diff --git a/tsconfig.base.json b/tsconfig.base.json index 31381c82719626..a33f7b1cd960bd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1072,6 +1072,8 @@ "@kbn/oidc-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/oidc_provider/*"], "@kbn/open-telemetry-instrumented-plugin": ["test/common/plugins/otel_metrics"], "@kbn/open-telemetry-instrumented-plugin/*": ["test/common/plugins/otel_metrics/*"], + "@kbn/openapi-generator": ["packages/kbn-openapi-generator"], + "@kbn/openapi-generator/*": ["packages/kbn-openapi-generator/*"], "@kbn/optimizer": ["packages/kbn-optimizer"], "@kbn/optimizer/*": ["packages/kbn-optimizer/*"], "@kbn/optimizer-webpack-helpers": ["packages/kbn-optimizer-webpack-helpers"], diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts index 9bb7d32c19645b..c99e7d77da8300 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/warning_schema.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type WarningSchema = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts index 9f9abdf51dc4e6..f68164f5bc12b0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type GetPrebuiltRulesAndTimelinesStatusResponse = z.infer< @@ -20,30 +20,30 @@ export const GetPrebuiltRulesAndTimelinesStatusResponse = z /** * The total number of custom rules */ - rules_custom_installed: z.number().min(0), + rules_custom_installed: z.number().int().min(0), /** * The total number of installed prebuilt rules */ - rules_installed: z.number().min(0), + rules_installed: z.number().int().min(0), /** * The total number of available prebuilt rules that are not installed */ - rules_not_installed: z.number().min(0), + rules_not_installed: z.number().int().min(0), /** * The total number of outdated prebuilt rules */ - rules_not_updated: z.number().min(0), + rules_not_updated: z.number().int().min(0), /** * The total number of installed prebuilt timelines */ - timelines_installed: z.number().min(0), + timelines_installed: z.number().int().min(0), /** * The total number of available prebuilt timelines that are not installed */ - timelines_not_installed: z.number().min(0), + timelines_not_installed: z.number().int().min(0), /** * The total number of outdated prebuilt timelines */ - timelines_not_updated: z.number().min(0), + timelines_not_updated: z.number().int().min(0), }) .strict(); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts index a36476054dfdc7..36a15ffc5f7018 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts index b9557d98e87f97..546878e699cd91 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type DetailsRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts index e1176c167fcf49..dbd24eef454d31 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts index 0b70a7676e069d..945a03f0d38d2f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type FileDownloadRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts index 1e4e7813d35b8f..ef9a40462334e9 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type FileInfoRequestParams = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts index af03b239d39138..785a4a1097e0cd 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts index d8109d433fab44..648f1700a54ca1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { BaseActionSchema } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts index f734092c220581..32844921170bd2 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { @@ -31,7 +31,7 @@ export const ListRequestQuery = z.object({ /** * Number of items per page */ - pageSize: z.number().min(1).max(10000).optional().default(10), + pageSize: z.number().int().min(1).max(10000).optional().default(10), startDate: StartDate.optional(), endDate: EndDate.optional(), userIds: UserIds.optional(), diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts index fca1370559ada4..d56463621d3c89 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type ListRequestQuery = z.infer; @@ -17,11 +17,11 @@ export const ListRequestQuery = z.object({ /** * Page number */ - page: z.number().min(0).optional().default(0), + page: z.number().int().min(0).optional().default(0), /** * Number of items per page */ - pageSize: z.number().min(1).max(10000).optional().default(10), + pageSize: z.number().int().min(1).max(10000).optional().default(10), kuery: z.string().nullable().optional(), sortField: z .enum([ diff --git a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts index 89f15504c4be5e..2dffbc473ecc6f 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/model/schema/common.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ export type Id = z.infer; @@ -22,13 +22,13 @@ export const IdOrUndefined = Id.nullable(); * Page number */ export type Page = z.infer; -export const Page = z.number().min(1).default(1); +export const Page = z.number().int().min(1).default(1); /** * Number of items per page */ export type PageSize = z.infer; -export const PageSize = z.number().min(1).max(100).default(10); +export const PageSize = z.number().int().min(1).max(100).default(10); /** * Start date @@ -65,6 +65,8 @@ export const Command = z.enum([ 'execute', 'upload', ]); +export const CommandEnum = Command.enum; +export type CommandEnum = typeof Command.enum; export type Commands = z.infer; export const Commands = z.array(Command); @@ -73,10 +75,12 @@ export const Commands = z.array(Command); * The maximum timeout value in milliseconds (optional) */ export type Timeout = z.infer; -export const Timeout = z.number().min(1); +export const Timeout = z.number().int().min(1); export type Status = z.infer; export const Status = z.enum(['failed', 'pending', 'successful']); +export const StatusEnum = Status.enum; +export type StatusEnum = typeof Status.enum; export type Statuses = z.infer; export const Statuses = z.array(Status); @@ -95,6 +99,8 @@ export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string( export type Type = z.infer; export const Type = z.enum(['automated', 'manual']); +export const TypeEnum = Type.enum; +export type TypeEnum = typeof Type.enum; export type Types = z.infer; export const Types = z.array(Type); @@ -143,7 +149,7 @@ export const ProcessActionSchemas = BaseActionSchema.and( z.object({ parameters: z.union([ z.object({ - pid: z.number().min(1).optional(), + pid: z.number().int().min(1).optional(), }), z.object({ entity_id: z.string().min(1).optional(), diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts index 3e511f7b4aad47..0b81cbdccd8825 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { SuccessResponse, AgentId } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts index a827c94d3b5fdb..c1bc91e6ded867 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.gen.ts @@ -9,7 +9,7 @@ import { z } from 'zod'; /* * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator `yarn openapi:generate`. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ import { SuccessResponse } from '../model/schema/common.gen'; diff --git a/x-pack/plugins/security_solution/scripts/openapi/generate.js b/x-pack/plugins/security_solution/scripts/openapi/generate.js index bd88357a3754d0..77446122827505 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/generate.js +++ b/x-pack/plugins/security_solution/scripts/openapi/generate.js @@ -6,6 +6,13 @@ */ require('../../../../../src/setup_node_env'); -const { generate } = require('./openapi_generator'); +const { generate } = require('@kbn/openapi-generator'); +const { resolve } = require('path'); -generate(); +const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); + +generate({ + rootDir: SECURITY_SOLUTION_ROOT, + sourceGlob: './**/*.schema.yaml', + templateName: 'zod_operation_schema', +}); diff --git a/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts b/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts deleted file mode 100644 index 272e62061c6a49..00000000000000 --- a/x-pack/plugins/security_solution/scripts/openapi/openapi_generator.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* eslint-disable no-console */ - -import SwaggerParser from '@apidevtools/swagger-parser'; -import chalk from 'chalk'; -import fs from 'fs/promises'; -import globby from 'globby'; -import { resolve } from 'path'; -import { fixEslint } from './lib/fix_eslint'; -import { formatOutput } from './lib/format_output'; -import { removeGenArtifacts } from './lib/remove_gen_artifacts'; -import { getApiOperationsList } from './parsers/get_api_operations_list'; -import { getComponents } from './parsers/get_components'; -import { getImportsMap } from './parsers/get_imports_map'; -import type { OpenApiDocument } from './parsers/openapi_types'; -import { initTemplateService } from './template_service/template_service'; - -const ROOT_SECURITY_SOLUTION_FOLDER = resolve(__dirname, '../..'); -const COMMON_API_FOLDER = resolve(ROOT_SECURITY_SOLUTION_FOLDER, './common/api'); -const SCHEMA_FILES_GLOB = resolve(ROOT_SECURITY_SOLUTION_FOLDER, './**/*.schema.yaml'); -const GENERATED_ARTIFACTS_GLOB = resolve(COMMON_API_FOLDER, './**/*.gen.ts'); - -export const generate = async () => { - console.log(chalk.bold(`Generating API route schemas`)); - console.log(chalk.bold(`Working directory: ${chalk.underline(COMMON_API_FOLDER)}`)); - - console.log(`๐Ÿ‘€ Searching for schemas`); - const schemaPaths = await globby([SCHEMA_FILES_GLOB]); - - console.log(`๐Ÿ•ต๏ธโ€โ™€๏ธ Found ${schemaPaths.length} schemas, parsing`); - const parsedSchemas = await Promise.all( - schemaPaths.map(async (schemaPath) => { - const parsedSchema = (await SwaggerParser.parse(schemaPath)) as OpenApiDocument; - return { schemaPath, parsedSchema }; - }) - ); - - console.log(`๐Ÿงน Cleaning up any previously generated artifacts`); - await removeGenArtifacts(COMMON_API_FOLDER); - - console.log(`๐Ÿช„ Generating new artifacts`); - const TemplateService = await initTemplateService(); - await Promise.all( - parsedSchemas.map(async ({ schemaPath, parsedSchema }) => { - const components = getComponents(parsedSchema); - const apiOperations = getApiOperationsList(parsedSchema); - const importsMap = getImportsMap(parsedSchema); - - // If there are no operations or components to generate, skip this file - const shouldGenerate = apiOperations.length > 0 || components !== undefined; - if (!shouldGenerate) { - return; - } - - const result = TemplateService.compileTemplate('schemas', { - components, - apiOperations, - importsMap, - }); - - // Write the generation result to disk - await fs.writeFile(schemaPath.replace('.schema.yaml', '.gen.ts'), result); - }) - ); - - // Format the output folder using prettier as the generator produces - // unformatted code and fix any eslint errors - console.log(`๐Ÿ’… Formatting output`); - await formatOutput(GENERATED_ARTIFACTS_GLOB); - await fixEslint(GENERATED_ARTIFACTS_GLOB); -}; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index e73140fec20b72..3dbc778394ecbd 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -170,8 +170,8 @@ "@kbn/core-logging-server-mocks", "@kbn/core-lifecycle-browser", "@kbn/security-solution-features", - "@kbn/handlebars", "@kbn/content-management-plugin", - "@kbn/subscription-tracking" + "@kbn/subscription-tracking", + "@kbn/openapi-generator" ] } diff --git a/yarn.lock b/yarn.lock index 83eb6e673dc60c..84df6498ff2507 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5087,6 +5087,10 @@ version "0.0.0" uid "" +"@kbn/openapi-generator@link:packages/kbn-openapi-generator": + version "0.0.0" + uid "" + "@kbn/optimizer-webpack-helpers@link:packages/kbn-optimizer-webpack-helpers": version "0.0.0" uid "" From 816c51d66040e544a82fb38d77323f20f55067de Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Fri, 22 Sep 2023 18:21:09 +0200 Subject: [PATCH 2/2] Update packages/kbn-openapi-generator/src/cli.ts Co-authored-by: Garrett Spong --- packages/kbn-openapi-generator/src/cli.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/kbn-openapi-generator/src/cli.ts b/packages/kbn-openapi-generator/src/cli.ts index 722fc6123e899e..9eb91dd9bba946 100644 --- a/packages/kbn-openapi-generator/src/cli.ts +++ b/packages/kbn-openapi-generator/src/cli.ts @@ -1,11 +1,3 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License