[Security Solution] Extract OpenAPI codegen to a package (#166269)

This commit is contained in:
Dmitrii Shevchenko 2023-09-25 10:51:40 +02:00 committed by GitHub
parent c7f49c200c
commit 38e6b76640
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 707 additions and 208 deletions

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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",

View file

@ -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<typeof InstallPrebuiltRulesResponse>;
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'
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View file

@ -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';

View file

@ -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: ['<rootDir>/packages/kbn-openapi-generator'],
};

View file

@ -0,0 +1,6 @@
{
"devOnly": true,
"id": "@kbn/openapi-generator",
"owner": "@elastic/security-detection-engine",
"type": "shared-common"
}

View file

@ -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"
}

View file

@ -0,0 +1,44 @@
/*
* 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();
}

View file

@ -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,
});
}

View file

@ -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';

View file

@ -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');
}

View file

@ -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';

View file

@ -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);
};

View file

@ -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,
};
}

View file

@ -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;
});
}
);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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 === '';
});
}

View file

@ -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';

View file

@ -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<ITemplateService> => {
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);
},
};

View file

@ -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.
*/

View file

@ -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<typeof {{@key}}>;
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<typeof {{operationId}}RequestQuery>;
export const {{operationId}}RequestQuery = {{> schema_item requestQuery }};
export const {{operationId}}RequestQuery = {{> zod_query_item requestQuery }};
export type {{operationId}}RequestQueryInput = z.input<typeof {{operationId}}RequestQuery>;
{{/if}}
@ -45,7 +49,7 @@ export type {{operationId}}RequestQueryInput = z.input<typeof {{operationId}}Req
*/
{{/if}}
export type {{operationId}}RequestParams = z.infer<typeof {{operationId}}RequestParams>;
export const {{operationId}}RequestParams = {{> schema_item requestParams }};
export const {{operationId}}RequestParams = {{> zod_schema_item requestParams }};
export type {{operationId}}RequestParamsInput = z.input<typeof {{operationId}}RequestParams>;
{{/if}}
@ -56,7 +60,7 @@ export type {{operationId}}RequestParamsInput = z.input<typeof {{operationId}}Re
*/
{{/if}}
export type {{operationId}}RequestBody = z.infer<typeof {{operationId}}RequestBody>;
export const {{operationId}}RequestBody = {{> schema_item requestBody }};
export const {{operationId}}RequestBody = {{> zod_schema_item requestBody }};
export type {{operationId}}RequestBodyInput = z.input<typeof {{operationId}}RequestBody>;
{{/if}}
@ -67,6 +71,6 @@ export type {{operationId}}RequestBodyInput = z.input<typeof {{operationId}}Requ
*/
{{/if}}
export type {{operationId}}Response = z.infer<typeof {{operationId}}Response>;
export const {{operationId}}Response = {{> schema_item response }};
export const {{operationId}}Response = {{> zod_schema_item response }};
{{/if}}
{{/each}}

View file

@ -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~}}

View file

@ -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}}

View file

@ -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",
]
}

View file

@ -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();

View file

@ -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"],

View file

@ -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<typeof WarningSchema>;

View file

@ -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();

View file

@ -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';

View file

@ -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<typeof DetailsRequestParams>;

View file

@ -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';

View file

@ -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<typeof FileDownloadRequestParams>;

View file

@ -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<typeof FileInfoRequestParams>;

View file

@ -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';

View file

@ -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';

View file

@ -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(),

View file

@ -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<typeof ListRequestQuery>;
@ -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([

View file

@ -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<typeof Id>;
@ -22,13 +22,13 @@ export const IdOrUndefined = Id.nullable();
* Page number
*/
export type Page = z.infer<typeof Page>;
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<typeof PageSize>;
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<typeof Commands>;
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<typeof Timeout>;
export const Timeout = z.number().min(1);
export const Timeout = z.number().int().min(1);
export type Status = z.infer<typeof Status>;
export const Status = z.enum(['failed', 'pending', 'successful']);
export const StatusEnum = Status.enum;
export type StatusEnum = typeof Status.enum;
export type Statuses = z.infer<typeof Statuses>;
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<typeof Type>;
export const Type = z.enum(['automated', 'manual']);
export const TypeEnum = Type.enum;
export type TypeEnum = typeof Type.enum;
export type Types = z.infer<typeof Types>;
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(),

View file

@ -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';

View file

@ -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';

View file

@ -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',
});

View file

@ -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);
};

View file

@ -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"
]
}

View file

@ -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 ""