Merge pull request #23610 from aramissennyeydd/openapi-tooling/generated-server

feat(openapi-tooling): stop inferring server types
This commit is contained in:
Patrik Oldsberg
2024-10-29 16:14:40 +01:00
committed by GitHub
151 changed files with 3270 additions and 239 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-client': minor
---
Internal update to use the updated generated code from `backstage-cli package schema openapi generate --client-package ...`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': minor
---
**BREAKING**: Updates ESLint config to ignore all generated source code under `src/**/generated/**/*.ts`.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog-backend': minor
'@backstage/plugin-search-backend': minor
---
Internal update to use the new generated server types from `backstage-cli package schema openapi generate --server`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': minor
---
`backstage-repo-tools package schema openapi generate --server` now generates complete TS interfaces for all request/response objects in your OpenAPI schema. This fixes an edge case around recursive schemas and standardizes both the generated client and server to have similar generated types.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-openapi-utils': minor
---
Adds a new `createValidatedOpenApiRouterFromGeneratedEndpointMap` function that uses the new static server generation in `backstage-cli package schema openapi generate --server` to create a typed express router.
+9 -7
View File
@@ -35,10 +35,10 @@ There are two required npm packages before we start,
1. `@backstage/repo-tools`, this package contains all OpenAPI-related commands for your plugins. We will be using this throughout the tutorial.
2. `@useoptic/optic`, this package is a dependency of `@backstage/repo-tools` but is only required for OpenAPI-related commands.
Further, for generating the client a `java` binary has to be available on your PATH.
You should install both of the above packages in the _root_ of your workspace.
Further, a `java` binary has to be available on your PATH.
## Storing your OpenAPI specification
You should create a new folder, `src/schema` in your backend plugin to store your OpenAPI (and any other) specifications. For example, if you're adding a specification to the catalog plugin, you would add a `src/schema` folder to `plugins/catalog-backend`, making a `plugins/catalog-backend/src/schema` directory. This directory should have an `openapi.yaml` file inside.
@@ -47,12 +47,14 @@ You should create a new folder, `src/schema` in your backend plugin to store you
## Generating a typed express router from a spec
Run `yarn backstage-repo-tools package schema openapi generate --server` from the directory with your plugin. This will create an `openapi.generated.ts` file in the `src/schema` directory that contains the OpenAPI schema as well as a generated express router with types. You should add this command to your `package.json` for future use, and you can combine both the server generation and the client generation below, like so, `yarn backstage-repo-tools package schema openapi generate --server --client-package <clientPackageDirectory>`
Run `yarn backstage-repo-tools package schema openapi generate --server` from the directory with your plugin. This will create a `router.ts` file in the `src/schema/openapi/generated` directory that contains the OpenAPI schema as well as a factory function for a generated express router with types that match your schema.
You should add this command to your `package.json` for future use and you can combine both the server generation and the client generation below like so, `yarn backstage-repo-tools package schema openapi generate --server --client-package <clientPackageDirectory>`
Use it like so, update your `router.ts` or `createRouter.ts` file with the following content,
```diff
+ import { createOpenApiRouter } from '../schema/openapi.generated';
+ import { createOpenApiRouter } from '../schema/openapi';
- import Router from 'express-promise-router';
...
@@ -65,12 +67,12 @@ export async function createRouter(
## Generating a typed client from a spec
From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate --client-package <plugin-client-directory>`. `<plugin-client-directory>` is a new directory and npm package that you should create. The general pattern is `plugins/<plugin-name>-client` or if you want to co-locate this with your other shared types, use `plugins/<plugin-name>-common`. You should add this command to your `package.json` for future use.
From your current backend plugin directory, run `yarn backstage-repo-tools package schema openapi generate --client-package <plugin-client-directory>`. `<plugin-client-directory>` is a new directory and npm package that you should create. The general pattern is to add a new entry point to your plugin's common package, `plugins/<plugin-name>-common/client`. You should add this command to your `package.json` for future use.
The generated client will have a directory `src/generated` that exports a `DefaultApiClient` class and all generated types. You can use the client like so,
The generated client will have a directory `src/schema/openapi/generated` that exports a `DefaultApiClient` class and all generated types. You can use the client like so,
```diff
+ import { DefaultApiClient } from './generated';
+ import { DefaultApiClient } from '../schema/openapi/generated';
export class CatalogClient implements CatalogApi {
+ private readonly apiClient: DefaultApiClient;
+4 -6
View File
@@ -20,9 +20,7 @@ info:
### Generating your client
1. Run `yarn backstage-repo-tools package schema openapi generate client --client-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content.
2. You should use the generated files as follows,
- `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API.
- `models/*` - These are the types generated from your OpenAPI file, ideally you should not need to use these directly and can instead use the inferred types from `apis/DefaultApi.client.ts`.
- everything else is directory specific and shouldn't be touched.
1. Run `yarn backstage-repo-tools package schema openapi generate --client-package <directory>`. This will create a new folder in `<directory>/src/schema/openapi/generated` to house the generated content.
2. You should not need to import anything from subfolders of the `src/schema/openapi/generated` parent folder, everything you should require will be accessible from the `src/schema/openapi/generated/index.ts` file. Of note,
3. `DefaultApiClient` - this is the client that you can use to access your specific spec.
4. Any request or response types - these will be available from the index and should match the names in your spec.
+210 -1
View File
@@ -102,12 +102,45 @@ export function createValidatedOpenApiRouter<T extends RequiredDoc>(
},
): ApiRouter<T>;
// @public
export function createValidatedOpenApiRouterFromGeneratedEndpointMap<
T extends EndpointMap,
>(
spec: RequiredDoc,
options?: {
validatorOptions?: Partial<Parameters<typeof middleware>['0']>;
middleware?: RequestHandler[];
},
): TypedRouter<T>;
// @public (undocumented)
type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = Extract<
T,
Record<K, V>
>;
// @public (undocumented)
type DocEndpoint<Doc extends EndpointMap> = ValueOf<{
[Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}`
? Endpoint
: never;
}>;
// @public (undocumented)
type DocEndpointMethod<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}`
? Method
: never;
}>;
// @public (undocumented)
type DocEndpointTemplate<Doc extends EndpointMap> = PathTemplate<
DocEndpoint<Doc>
>;
// @public (undocumented)
type DocOperation<
Doc extends RequiredDoc,
@@ -239,6 +272,85 @@ interface DocRequestMatcher<
): T;
}
// @public (undocumented)
type EndpointMap = Record<
string,
{
query?: object;
body?: object;
response?: object | void;
path?: object;
}
>;
// @public
type EndpointMapRequestHandler<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandler<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
// @public
type EndpointMapRequestHandlerParams<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandlerParams<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
// @public
interface EndpointMapRequestMatcher<
Doc extends EndpointMap,
T,
Method extends HttpMethods,
> {
// (undocumented)
<
TPath extends MethodAwareDocEndpoints<
Doc,
DocEndpoint<Doc>,
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
>,
TMethod extends Method &
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
>(
path: TPath,
...handlers: Array<
EndpointMapRequestHandler<Doc, TemplateToDocEndpoint<Doc, TPath>, TMethod>
>
): T;
// (undocumented)
<
TPath extends MethodAwareDocEndpoints<
Doc,
DocEndpoint<Doc>,
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
>,
TMethod extends Method &
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
>(
path: TPath,
...handlers: Array<
EndpointMapRequestHandlerParams<
Doc,
TemplateToDocEndpoint<Doc, TPath>,
TMethod
>
>
): T;
}
// @public (undocumented)
type Filter<T, U> = T extends U ? T : never;
@@ -278,6 +390,9 @@ type HeaderSchema<
Method extends DocPathMethod<Doc, Path>,
> = ParametersSchema<Doc, Path, Method, ImmutableHeaderObject>;
// @public (undocumented)
type HttpMethods = 'all' | 'put' | 'get' | 'post' | '_delete';
// @public
type Immutable<T> = T extends
| Function
@@ -401,6 +516,21 @@ declare namespace internal {
Response_3 as Response,
ResponseSchemas,
ResponseBodyToJsonSchema,
EndpointMap,
HttpMethods,
StaticPathParamsSchema,
StaticRequestBodySchema,
StaticResponseSchema,
StaticQueryParamsSchema,
EndpointMapRequestHandler,
EndpointMapRequestHandlerParams,
DocEndpoint,
DocEndpointMethod,
MethodAwareDocEndpoints,
DocEndpointTemplate,
TemplateToDocEndpoint,
EndpointMapRequestMatcher,
TypedRouter,
};
}
export { internal };
@@ -427,6 +557,19 @@ type MapToSchema<
: never;
};
// @public (undocumented)
type MethodAwareDocEndpoints<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${Method}|${infer E}`
? E extends DocEndpoint<Doc>
? PathTemplate<E>
: never
: never;
}>;
// @public (undocumented)
type MethodAwareDocPath<
Doc extends PathDoc,
@@ -513,7 +656,7 @@ type PathSchema<
> = ParametersSchema<Doc, Path, Method, ImmutablePathObject>;
// @public
type PathTemplate<Path extends string> =
export type PathTemplate<Path extends string> =
Path extends `${infer Prefix}{${infer PathName}}${infer Suffix}`
? `${Prefix}:${PathName}${PathTemplate<Suffix>}`
: Path;
@@ -684,6 +827,60 @@ type SchemaRef<Doc extends RequiredDoc, Schema> = Schema extends {
[Key in keyof Schema]: SchemaRef<Doc, Schema[Key]>;
};
// @public (undocumented)
type StaticPathParamsSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'path' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['path']
: never
: never;
// @public (undocumented)
type StaticQueryParamsSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'query' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['query']
: never
: never;
// @public (undocumented)
type StaticRequestBodySchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'body' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['body']
: unknown
: unknown;
// @public (undocumented)
type StaticResponseSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'response' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['response']
: unknown
: unknown;
// @public (undocumented)
type TemplateToDocEndpoint<
Doc extends EndpointMap,
Path extends DocEndpointTemplate<Doc>,
> = ValueOf<{
[Template in DocEndpoint<Doc>]: Path extends PathTemplate<Template>
? Template
: never;
}>;
// @public
type TemplateToDocPath<
Doc extends PathDoc,
@@ -704,6 +901,18 @@ type TuplifyUnion<
N = [T] extends [never] ? true : false,
> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>;
// @public (undocumented)
interface TypedRouter<GeneratedEndpointMap extends EndpointMap> extends Router {
// (undocumented)
delete: EndpointMapRequestMatcher<GeneratedEndpointMap, this, '_delete'>;
// (undocumented)
get: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'get'>;
// (undocumented)
post: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'post'>;
// (undocumented)
put: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'put'>;
}
// @public
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
+6 -1
View File
@@ -31,5 +31,10 @@ export type {
PathParameters,
} from './utility';
export type { ApiRouter } from './router';
export { createValidatedOpenApiRouter, getOpenApiSpecRoute } from './stub';
export type { PathTemplate } from './types/common';
export { wrapInOpenApiTestServer, wrapServer } from './testUtils';
export {
createValidatedOpenApiRouter,
getOpenApiSpecRoute,
createValidatedOpenApiRouterFromGeneratedEndpointMap,
} from './stub';
+44 -8
View File
@@ -16,7 +16,7 @@
import PromiseRouter from 'express-promise-router';
import { ApiRouter } from './router';
import { RequiredDoc } from './types';
import { EndpointMap, RequiredDoc, TypedRouter } from './types';
import {
ErrorRequestHandler,
RequestHandler,
@@ -24,6 +24,7 @@ import {
Request,
Response,
json,
Router,
} from 'express';
import { InputError } from '@backstage/errors';
import { middleware as OpenApiValidator } from 'express-openapi-validator';
@@ -58,19 +59,19 @@ export function getOpenApiSpecRoute(baseUrl: string) {
}
/**
* Create a new OpenAPI router with some default middleware.
* Create a router with validation middleware. This is used by typing methods to create an
* "OpenAPI router" with all of the expected validation + metadata.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
* @public
*/
export function createValidatedOpenApiRouter<T extends RequiredDoc>(
spec: T,
function createRouterWithValidation(
spec: RequiredDoc,
options?: {
validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;
middleware?: RequestHandler[];
},
) {
): Router {
const router = PromiseRouter();
router.use(options?.middleware || getDefaultRouterMiddleware());
@@ -152,6 +153,41 @@ export function createValidatedOpenApiRouter<T extends RequiredDoc>(
}
res.json(mergeOutput.output);
});
return router as ApiRouter<typeof spec>;
return router;
}
/**
* Create a new OpenAPI router with some default middleware.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
* @public
*/
export function createValidatedOpenApiRouter<T extends RequiredDoc>(
spec: T,
options?: {
validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;
middleware?: RequestHandler[];
},
) {
return createRouterWithValidation(spec, options) as ApiRouter<typeof spec>;
}
/**
* Create a new OpenAPI router with some default middleware.
* @param spec - Your OpenAPI spec imported as a JSON object.
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
* @returns A new express router with validation middleware.
* @public
*/
export function createValidatedOpenApiRouterFromGeneratedEndpointMap<
T extends EndpointMap,
>(
spec: RequiredDoc,
options?: {
validatorOptions?: Partial<Parameters<typeof OpenApiValidator>['0']>;
middleware?: RequestHandler[];
},
) {
return createRouterWithValidation(spec, options) as TypedRouter<T>;
}
@@ -0,0 +1,222 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Router } from 'express';
import type core from 'express-serve-static-core';
import { PathTemplate, ValueOf } from './common';
/**
* @public
*/
export type EndpointMap = Record<
string,
{ query?: object; body?: object; response?: object | void; path?: object }
>;
// OpenAPI generator doesn't emit regular lowercase 'delete'.
/**
* @public
*/
export type HttpMethods = 'all' | 'put' | 'get' | 'post' | '_delete';
/**
* @public
*/
export type StaticPathParamsSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'path' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['path']
: never
: never;
/**
* @public
*/
export type StaticRequestBodySchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'body' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['body']
: unknown
: unknown;
/**
* @public
*/
export type StaticResponseSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'response' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['response']
: unknown
: unknown;
/**
* @public
*/
export type StaticQueryParamsSchema<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = `#${Method}|${Endpoint}` extends keyof Doc
? 'query' extends keyof Doc[`#${Method}|${Endpoint}`]
? Doc[`#${Method}|${Endpoint}`]['query']
: never
: never;
/**
* Typed express request handler.
* @public
*/
export type EndpointMapRequestHandler<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandler<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
/**
* Typed express error handler / request handler union type.
* @public
*/
export type EndpointMapRequestHandlerParams<
Doc extends EndpointMap,
Path extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Path>,
> = core.RequestHandlerParams<
StaticPathParamsSchema<Doc, Path, Method>,
StaticResponseSchema<Doc, Path, Method>,
StaticRequestBodySchema<Doc, Path, Method>,
StaticQueryParamsSchema<Doc, Path, Method>,
Record<string, string>
>;
/**
* @public
*/
export type DocEndpoint<Doc extends EndpointMap> = ValueOf<{
[Template in keyof Doc]: Template extends `#${string}|${infer Endpoint}`
? Endpoint
: never;
}>;
/**
* @public
*/
export type DocEndpointMethod<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${infer Method}|${Endpoint}`
? Method
: never;
}>;
/**
* @public
*/
export type MethodAwareDocEndpoints<
Doc extends EndpointMap,
Endpoint extends DocEndpoint<Doc>,
Method extends DocEndpointMethod<Doc, Endpoint>,
> = ValueOf<{
[Template in keyof Doc]: Template extends `#${Method}|${infer E}`
? E extends DocEndpoint<Doc>
? PathTemplate<E>
: never
: never;
}>;
/**
* @public
*/
export type DocEndpointTemplate<Doc extends EndpointMap> = PathTemplate<
DocEndpoint<Doc>
>;
/**
* @public
*/
export type TemplateToDocEndpoint<
Doc extends EndpointMap,
Path extends DocEndpointTemplate<Doc>,
> = ValueOf<{
[Template in DocEndpoint<Doc>]: Path extends PathTemplate<Template>
? Template
: never;
}>;
/**
* Superset of the express router path matcher that enforces typed request and response bodies.
* @public
*/
export interface EndpointMapRequestMatcher<
Doc extends EndpointMap,
T,
Method extends HttpMethods,
> {
<
TPath extends MethodAwareDocEndpoints<
Doc,
DocEndpoint<Doc>,
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
>,
TMethod extends Method &
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
>(
path: TPath,
...handlers: Array<
EndpointMapRequestHandler<Doc, TemplateToDocEndpoint<Doc, TPath>, TMethod>
>
): T;
<
TPath extends MethodAwareDocEndpoints<
Doc,
DocEndpoint<Doc>,
Method & DocEndpointMethod<Doc, DocEndpoint<Doc>>
>,
TMethod extends Method &
DocEndpointMethod<Doc, TemplateToDocEndpoint<Doc, TPath>>,
>(
path: TPath,
...handlers: Array<
EndpointMapRequestHandlerParams<
Doc,
TemplateToDocEndpoint<Doc, TPath>,
TMethod
>
>
): T;
}
/**
* @public
*/
export interface TypedRouter<GeneratedEndpointMap extends EndpointMap>
extends Router {
get: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'get'>;
post: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'post'>;
put: EndpointMapRequestMatcher<GeneratedEndpointMap, this, 'put'>;
delete: EndpointMapRequestMatcher<GeneratedEndpointMap, this, '_delete'>;
}
@@ -20,3 +20,4 @@ export * from './immutable';
export * from './params';
export * from './requests';
export * from './responses';
export * from './generated';
+1 -1
View File
@@ -42,7 +42,7 @@ import {
ValidateEntityResponse,
} from './types/api';
import { isQueryEntitiesInitialRequest, splitRefsIntoChunks } from './utils';
import { DefaultApiClient, TypedResponse } from './generated';
import { DefaultApiClient, TypedResponse } from './schema/openapi';
/**
* A frontend and backend compatible client for communicating with the Backstage
@@ -56,8 +56,149 @@ export interface RequestOptions {
token?: string;
}
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
};
/**
* @public
*/
export type DeleteEntityByUid = {
path: {
uid: string;
};
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
};
/**
* @public
*/
export type GetEntities = {
query: {
fields?: Array<string>;
limit?: number;
filter?: Array<string>;
offset?: number;
after?: string;
order?: Array<string>;
};
};
/**
* @public
*/
export type GetEntitiesByQuery = {
query: {
fields?: Array<string>;
limit?: number;
offset?: number;
orderField?: Array<string>;
cursor?: string;
filter?: Array<string>;
fullTextFilterTerm?: string;
fullTextFilterFields?: Array<string>;
};
};
/**
* @public
*/
export type GetEntitiesByRefs = {
body: GetEntitiesByRefsRequest;
query: {
filter?: Array<string>;
};
};
/**
* @public
*/
export type GetEntityAncestryByName = {
path: {
kind: string;
namespace: string;
name: string;
};
};
/**
* @public
*/
export type GetEntityByName = {
path: {
kind: string;
namespace: string;
name: string;
};
};
/**
* @public
*/
export type GetEntityByUid = {
path: {
uid: string;
};
};
/**
* @public
*/
export type GetEntityFacets = {
query: {
facet: Array<string>;
filter?: Array<string>;
};
};
/**
* @public
*/
export type GetLocation = {
path: {
id: string;
};
};
/**
* @public
*/
export type GetLocationByEntity = {
path: {
kind: string;
namespace: string;
name: string;
};
};
/**
* @public
*/
export type GetLocations = {};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
};
/**
* no description
* @public
*/
export class DefaultApiClient {
private readonly discoveryApi: DiscoveryApi;
@@ -73,13 +214,11 @@ export class DefaultApiClient {
/**
* Validate a given location.
* @param analyzeLocationRequest
* @param analyzeLocationRequest -
*/
public async analyzeLocation(
// @ts-ignore
request: {
body: AnalyzeLocationRequest;
},
request: AnalyzeLocation,
options?: RequestOptions,
): Promise<TypedResponse<AnalyzeLocationResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -100,17 +239,12 @@ export class DefaultApiClient {
/**
* Create a location for a given target.
* @param createLocationRequest
* @param dryRun
* @param createLocationRequest -
* @param dryRun -
*/
public async createLocation(
// @ts-ignore
request: {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
},
request: CreateLocation,
options?: RequestOptions,
): Promise<TypedResponse<CreateLocation201Response>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -133,15 +267,11 @@ export class DefaultApiClient {
/**
* Delete a single entity by UID.
* @param uid
* @param uid -
*/
public async deleteEntityByUid(
// @ts-ignore
request: {
path: {
uid: string;
};
},
request: DeleteEntityByUid,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -163,15 +293,11 @@ export class DefaultApiClient {
/**
* Delete a location by id.
* @param id
* @param id -
*/
public async deleteLocation(
// @ts-ignore
request: {
path: {
id: string;
};
},
request: DeleteLocation,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -193,25 +319,16 @@ export class DefaultApiClient {
/**
* Get all entities matching a given filter.
* @param fields Restrict to just these fields in the response.
* @param limit Number of records to return in the response.
* @param filter Filter for just the entities defined by this filter.
* @param offset Number of records to skip in the query page.
* @param after Pointer to the previous page of results.
* @param order
* @param fields - Restrict to just these fields in the response.
* @param limit - Number of records to return in the response.
* @param filter - Filter for just the entities defined by this filter.
* @param offset - Number of records to skip in the query page.
* @param after - Pointer to the previous page of results.
* @param order -
*/
public async getEntities(
// @ts-ignore
request: {
query: {
fields?: Array<string>;
limit?: number;
filter?: Array<string>;
offset?: number;
after?: string;
order?: Array<string>;
};
},
request: GetEntities,
options?: RequestOptions,
): Promise<TypedResponse<Array<Entity>>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -233,28 +350,18 @@ export class DefaultApiClient {
/**
* Search for entities by a given query.
* @param fields Restrict to just these fields in the response.
* @param limit Number of records to return in the response.
* @param orderField The fields to sort returned results by.
* @param cursor Cursor to a set page of results.
* @param filter Filter for just the entities defined by this filter.
* @param fullTextFilterTerm Text search term.
* @param fullTextFilterFields A comma separated list of fields to sort returned results by.
* @param fields - Restrict to just these fields in the response.
* @param limit - Number of records to return in the response.
* @param offset - Number of records to skip in the query page.
* @param orderField - The fields to sort returned results by.
* @param cursor - Cursor to a set page of results.
* @param filter - Filter for just the entities defined by this filter.
* @param fullTextFilterTerm - Text search term.
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
*/
public async getEntitiesByQuery(
// @ts-ignore
request: {
query: {
fields?: Array<string>;
limit?: number;
offset?: number;
orderField?: Array<string>;
cursor?: string;
filter?: Array<string>;
fullTextFilterTerm?: string;
fullTextFilterFields?: Array<string>;
};
},
request: GetEntitiesByQuery,
options?: RequestOptions,
): Promise<TypedResponse<EntitiesQueryResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -276,23 +383,21 @@ export class DefaultApiClient {
/**
* Get a batch set of entities given an array of entityRefs.
* @param getEntitiesByRefsRequest
* @param filter - Filter for just the entities defined by this filter.
* @param getEntitiesByRefsRequest -
*/
public async getEntitiesByRefs(
// @ts-ignore
request: {
body: GetEntitiesByRefsRequest;
query?: {
filter?: Array<string>;
};
},
request: GetEntitiesByRefs,
options?: RequestOptions,
): Promise<TypedResponse<EntitiesBatchResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
const uriTemplate = `/entities/by-refs/{?filter*}`;
const uriTemplate = `/entities/by-refs{?filter*}`;
const uri = parser.parse(uriTemplate).expand({ ...request.query });
const uri = parser.parse(uriTemplate).expand({
...request.query,
});
return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
headers: {
@@ -306,19 +411,13 @@ export class DefaultApiClient {
/**
* Get an entity's ancestry by entity ref.
* @param kind
* @param namespace
* @param name
* @param kind -
* @param namespace -
* @param name -
*/
public async getEntityAncestryByName(
// @ts-ignore
request: {
path: {
kind: string;
namespace: string;
name: string;
};
},
request: GetEntityAncestryByName,
options?: RequestOptions,
): Promise<TypedResponse<EntityAncestryResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -342,19 +441,13 @@ export class DefaultApiClient {
/**
* Get an entity by an entity ref.
* @param kind
* @param namespace
* @param name
* @param kind -
* @param namespace -
* @param name -
*/
public async getEntityByName(
// @ts-ignore
request: {
path: {
kind: string;
namespace: string;
name: string;
};
},
request: GetEntityByName,
options?: RequestOptions,
): Promise<TypedResponse<Entity>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -378,15 +471,11 @@ export class DefaultApiClient {
/**
* Get a single entity by the UID.
* @param uid
* @param uid -
*/
public async getEntityByUid(
// @ts-ignore
request: {
path: {
uid: string;
};
},
request: GetEntityByUid,
options?: RequestOptions,
): Promise<TypedResponse<Entity>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -408,17 +497,12 @@ export class DefaultApiClient {
/**
* Get all entity facets that match the given filters.
* @param facet
* @param filter Filter for just the entities defined by this filter.
* @param facet -
* @param filter - Filter for just the entities defined by this filter.
*/
public async getEntityFacets(
// @ts-ignore
request: {
query: {
facet: Array<string>;
filter?: Array<string>;
};
},
request: GetEntityFacets,
options?: RequestOptions,
): Promise<TypedResponse<EntityFacetsResponse>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -440,15 +524,11 @@ export class DefaultApiClient {
/**
* Get a location by id.
* @param id
* @param id -
*/
public async getLocation(
// @ts-ignore
request: {
path: {
id: string;
};
},
request: GetLocation,
options?: RequestOptions,
): Promise<TypedResponse<Location>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -470,19 +550,13 @@ export class DefaultApiClient {
/**
* Get a location for entity.
* @param kind
* @param namespace
* @param name
* @param kind -
* @param namespace -
* @param name -
*/
public async getLocationByEntity(
// @ts-ignore
request: {
path: {
kind: string;
namespace: string;
name: string;
};
},
request: GetLocationByEntity,
options?: RequestOptions,
): Promise<TypedResponse<Location>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -509,7 +583,7 @@ export class DefaultApiClient {
*/
public async getLocations(
// @ts-ignore
request: {},
request: GetLocations,
options?: RequestOptions,
): Promise<TypedResponse<Array<GetLocations200ResponseInner>>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -529,13 +603,11 @@ export class DefaultApiClient {
/**
* Refresh the entity related to entityRef.
* @param refreshEntityRequest
* @param refreshEntityRequest -
*/
public async refreshEntity(
// @ts-ignore
request: {
body: RefreshEntityRequest;
},
request: RefreshEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -556,13 +628,11 @@ export class DefaultApiClient {
/**
* Validate that a passed in entity has no errors in schema.
* @param validateEntityRequest
* @param validateEntityRequest -
*/
public async validateEntity(
// @ts-ignore
request: {
body: ValidateEntityRequest;
},
request: ValidateEntity,
options?: RequestOptions,
): Promise<TypedResponse<void>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface AnalyzeLocationEntityField {
/**
* A text to show to the user to inform about the choices made. Like, it could say \"Found a CODEOWNERS file that covers this target, so we suggest leaving this field empty; which would currently make it owned by X\" where X is taken from the codeowners file.
@@ -34,6 +37,9 @@ export interface AnalyzeLocationEntityField {
field: string;
}
/**
* @public
*/
export type AnalyzeLocationEntityFieldStateEnum =
| 'analysisSuggestedValue'
| 'analysisSuggestedNoValue'
@@ -22,6 +22,7 @@ import { LocationSpec } from '../models/LocationSpec.model';
/**
* If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already
* @public
*/
export interface AnalyzeLocationExistingEntity {
entity: Entity;
@@ -22,6 +22,7 @@ import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model';
/**
* This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
* @public
*/
export interface AnalyzeLocationGenerateEntity {
fields: Array<AnalyzeLocationEntityField>;
@@ -19,6 +19,9 @@
// ******************************************************************
import { LocationInput } from '../models/LocationInput.model';
/**
* @public
*/
export interface AnalyzeLocationRequest {
catalogFileName?: string;
location: LocationInput;
@@ -20,6 +20,9 @@
import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model';
import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model';
/**
* @public
*/
export interface AnalyzeLocationResponse {
generateEntities: Array<AnalyzeLocationGenerateEntity>;
existingEntityFiles: Array<AnalyzeLocationExistingEntity>;
@@ -20,6 +20,9 @@
import { Entity } from '../models/Entity.model';
import { Location } from '../models/Location.model';
/**
* @public
*/
export interface CreateLocation201Response {
exists?: boolean;
entities: Array<Entity>;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface CreateLocationRequest {
target: string;
type: string;
@@ -19,6 +19,9 @@
// ******************************************************************
import { NullableEntity } from '../models/NullableEntity.model';
/**
* @public
*/
export interface EntitiesBatchResponse {
/**
* The list of entities, in the same order as the refs in the request. Entries that are null signify that no entity existed with that ref.
@@ -20,6 +20,9 @@
import { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model';
import { Entity } from '../models/Entity.model';
/**
* @public
*/
export interface EntitiesQueryResponse {
/**
* The list of entities paginated by a specific filter.
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface EntitiesQueryResponsePageInfo {
/**
* The cursor for the next batch of entities.
@@ -22,6 +22,7 @@ import { EntityRelation } from '../models/EntityRelation.model';
/**
* The parts of the format that's common to all versions/kinds of entity.
* @public
*/
export interface Entity {
/**
@@ -19,6 +19,9 @@
// ******************************************************************
import { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model';
/**
* @public
*/
export interface EntityAncestryResponse {
items: Array<EntityAncestryResponseItemsInner>;
rootEntityRef: string;
@@ -19,6 +19,9 @@
// ******************************************************************
import { Entity } from '../models/Entity.model';
/**
* @public
*/
export interface EntityAncestryResponseItemsInner {
parentEntityRefs: Array<string>;
entity: Entity;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface EntityFacet {
value: string;
count: number;
@@ -19,6 +19,9 @@
// ******************************************************************
import { EntityFacet } from '../models/EntityFacet.model';
/**
* @public
*/
export interface EntityFacetsResponse {
facets: { [key: string]: Array<EntityFacet> };
}
@@ -20,6 +20,7 @@
/**
* A link to external information that is related to the entity.
* @public
*/
export interface EntityLink {
/**
@@ -21,6 +21,7 @@ import { EntityLink } from '../models/EntityLink.model';
/**
* Metadata fields common to all versions/kinds of entity.
* @public
*/
export interface EntityMeta {
[key: string]: any;
@@ -20,6 +20,7 @@
/**
* A relation of a specific type to another entity in the catalog.
* @public
*/
export interface EntityRelation {
/**
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface ErrorError {
name: string;
message: string;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface ErrorRequest {
method: string;
url: string;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface ErrorResponse {
statusCode: number;
}
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface GetEntitiesByRefsRequest {
entityRefs: Array<string>;
fields?: Array<string>;
@@ -19,6 +19,9 @@
// ******************************************************************
import { Location } from '../models/Location.model';
/**
* @public
*/
export interface GetLocations200ResponseInner {
data: Location;
}
@@ -20,6 +20,7 @@
/**
* Entity location for a specific entity.
* @public
*/
export interface Location {
target: string;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface LocationInput {
type: string;
target: string;
@@ -20,6 +20,7 @@
/**
* Holds the entity location information.
* @public
*/
export interface LocationSpec {
target: string;
@@ -21,6 +21,9 @@ import { ErrorError } from '../models/ErrorError.model';
import { ErrorRequest } from '../models/ErrorRequest.model';
import { ErrorResponse } from '../models/ErrorResponse.model';
/**
* @public
*/
export interface ModelError {
[key: string]: any;
@@ -0,0 +1,45 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { EntityMeta } from '../models/EntityMeta.model';
import { EntityRelation } from '../models/EntityRelation.model';
/**
* The parts of the format that's common to all versions/kinds of entity.
* @public
*/
export type NullableEntity = {
/**
* The relations that this entity has with other entities.
*/
relations?: Array<EntityRelation>;
/**
* A type representing all allowed JSON object values.
*/
spec?: { [key: string]: any };
metadata: EntityMeta;
/**
* The high level entity type being described.
*/
kind: string;
/**
* The version of specification format for this particular entity that this is written against.
*/
apiVersion: string;
} | null;
@@ -22,6 +22,7 @@ import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntity
/**
* Makes all keys of an entire hierarchy optional.
* @public
*/
export interface RecursivePartialEntity {
/**
@@ -19,6 +19,9 @@
// ******************************************************************
import { EntityLink } from '../models/EntityLink.model';
/**
* @public
*/
export interface RecursivePartialEntityMeta {
/**
* A list of external hyperlinks related to the entity.
@@ -21,6 +21,7 @@ import { EntityLink } from '../models/EntityLink.model';
/**
* Metadata fields common to all versions/kinds of entity.
* @public
*/
export interface RecursivePartialEntityMetaAllOf {
/**
@@ -20,6 +20,7 @@
/**
* A relation of a specific type to another entity in the catalog.
* @public
*/
export interface RecursivePartialEntityRelation {
/**
@@ -20,6 +20,7 @@
/**
* Options for requesting a refresh of entities in the catalog.
* @public
*/
export interface RefreshEntityRequest {
authorizationToken?: string;
@@ -19,6 +19,9 @@
// ******************************************************************
import { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model';
/**
* @public
*/
export interface ValidateEntity400Response {
errors: Array<ValidateEntity400ResponseErrorsInner>;
}
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface ValidateEntity400ResponseErrorsInner {
[key: string]: any;
@@ -18,6 +18,9 @@
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface ValidateEntityRequest {
location: string;
entity: { [key: string]: any };
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './generated';
+1 -1
View File
@@ -167,7 +167,7 @@ function createConfig(dir, extraConfig = {}) {
},
},
{
files: ['**/src/generated/**/*.ts'],
files: ['**/src/**/generated/**/*.ts'],
rules: {
...tsRules,
'no-unused-vars': 'off',
@@ -63,7 +63,7 @@ async function generate(
'-c',
resolvePackagePath(
'@backstage/repo-tools',
'templates/typescript-backstage.yaml',
'templates/typescript-backstage-client.yaml',
),
'--generator-key',
'v3.0',
@@ -79,15 +79,21 @@ async function generate(
},
);
await exec(
`yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`,
[],
{ signal: abortSignal?.signal },
const parentDirectory = resolve(resolvedOutputDirectory, '..');
await fs.writeFile(
resolve(parentDirectory, 'index.ts'),
`//
export * from './generated';`,
);
await exec(`yarn backstage-cli package lint --fix ${parentDirectory}`, [], {
signal: abortSignal?.signal,
});
const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier');
if (prettier) {
await exec(`${prettier} --write ${resolvedOutputDirectory}`, [], {
await exec(`${prettier} --write ${parentDirectory}`, [], {
signal: abortSignal?.signal,
});
}
@@ -14,23 +14,39 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import YAML from 'js-yaml';
import chalk from 'chalk';
import { resolve, dirname, join } from 'path';
import YAML from 'js-yaml';
import {
OLD_SCHEMA_PATH,
OPENAPI_IGNORE_FILES,
OUTPUT_PATH,
TS_SCHEMA_PATH,
} from '../../../../../lib/openapi/constants';
import { paths as cliPaths } from '../../../../../lib/paths';
import { TS_SCHEMA_PATH } from '../../../../../lib/openapi/constants';
import { promisify } from 'util';
import { exec as execCb } from 'child_process';
import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers';
import fs from 'fs-extra';
import { exec } from '../../../../../lib/exec';
import { resolvePackagePath } from '@backstage/backend-plugin-api';
import {
getPathToCurrentOpenApiSpec,
getRelativePathToFile,
} from '../../../../../lib/openapi/helpers';
const exec = promisify(execCb);
async function generate(abortSignal?: AbortController) {
async function generateSpecFile() {
const openapiPath = await getPathToCurrentOpenApiSpec();
const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8'));
const tsPath = cliPaths.resolveTarget(TS_SCHEMA_PATH);
const schemaDir = dirname(tsPath);
await fs.mkdirp(schemaDir);
const oldTsPath = cliPaths.resolveTarget(OLD_SCHEMA_PATH);
if (fs.existsSync(oldTsPath)) {
console.warn(`Removing old schema file at ${oldTsPath}`);
fs.removeSync(oldTsPath);
}
// The first set of comment slashes allow for the eslint notice plugin to run
// with onNonMatchingHeader: 'replace', as is the case in the open source
// Backstage repo. Otherwise the auto-generated comment will be removed by the
@@ -42,23 +58,93 @@ async function generate(abortSignal?: AbortController) {
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import {createValidatedOpenApiRouter} from '@backstage/backend-openapi-utils';
import {createValidatedOpenApiRouterFromGeneratedEndpointMap} from '@backstage/backend-openapi-utils';
import {EndpointMap} from './';
export const spec = ${JSON.stringify(yaml, null, 2)} as const;
export const createOpenApiRouter = async (
options?: Parameters<typeof createValidatedOpenApiRouter>['1'],
) => createValidatedOpenApiRouter<typeof spec>(spec, options);
options?: Parameters<typeof createValidatedOpenApiRouterFromGeneratedEndpointMap>['1'],
) => createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>(spec, options);
`,
);
await exec(`yarn backstage-cli package lint --fix ${tsPath}`, {
signal: abortSignal?.signal,
});
const indexFile = join(schemaDir, '..', 'index.ts');
await fs.writeFile(
indexFile,
`//
export * from './generated';`,
);
await exec(`yarn backstage-cli package lint`, ['--fix', tsPath, indexFile]);
if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) {
await exec(`yarn prettier --write ${tsPath}`, {
await exec(`yarn prettier`, ['--write', tsPath, indexFile], {
cwd: cliPaths.targetRoot,
});
}
}
async function generate(abortSignal?: AbortController) {
const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec();
const resolvedOutputDirectory = await getRelativePathToFile(OUTPUT_PATH);
await fs.mkdirp(resolvedOutputDirectory);
await fs.writeFile(
resolve(resolvedOutputDirectory, '.openapi-generator-ignore'),
OPENAPI_IGNORE_FILES.join('\n'),
);
await exec(
'node',
[
resolvePackagePath('@openapitools/openapi-generator-cli', 'main.js'),
'generate',
'-i',
resolvedOpenapiPath,
'-o',
resolvedOutputDirectory,
'-g',
'typescript',
'-c',
resolvePackagePath(
'@backstage/repo-tools',
'templates/typescript-backstage-server.yaml',
),
'--generator-key',
'v3.0',
],
{
maxBuffer: Number.MAX_VALUE,
cwd: resolvePackagePath('@backstage/repo-tools'),
env: {
...process.env,
},
signal: abortSignal?.signal,
},
);
await exec(
`yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`,
[],
{
signal: abortSignal?.signal,
},
);
const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier');
if (prettier) {
await exec(`${prettier} --write ${resolvedOutputDirectory}`, [], {
signal: abortSignal?.signal,
});
}
fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore'));
fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), {
recursive: true,
force: true,
});
await generateSpecFile();
}
export async function command({
@@ -22,7 +22,7 @@ import { relative as relativePath, resolve as resolvePath } from 'path';
import { runner } from '../../../../lib/runner';
import { paths as cliPaths } from '../../../../lib/paths';
import {
TS_MODULE,
OLD_SCHEMA_PATH,
TS_SCHEMA_PATH,
YAML_SCHEMA_PATH,
} from '../../../../lib/openapi/constants';
@@ -41,12 +41,20 @@ async function verify(directoryPath: string) {
}
const yaml = await loadAndValidateOpenApiYaml(openapiPath);
const schemaPath = join(directoryPath, TS_SCHEMA_PATH);
if (!(await fs.pathExists(schemaPath))) {
let schemaPath = join(directoryPath, TS_SCHEMA_PATH);
if (
!(await fs.pathExists(schemaPath)) &&
!(await fs.pathExists(join(directoryPath, OLD_SCHEMA_PATH)))
) {
throw new Error(`No \`${TS_SCHEMA_PATH}\` file found.`);
} else if (await fs.pathExists(join(directoryPath, OLD_SCHEMA_PATH))) {
console.warn(
`\`${OLD_SCHEMA_PATH}\` is deprecated. Please re-run \`yarn backstage-repo-tools package schema openapi generate\` to update it.`,
);
schemaPath = join(directoryPath, OLD_SCHEMA_PATH);
}
const schema = await import(resolvePath(join(directoryPath, TS_MODULE)));
const schema = await import(resolvePath(schemaPath));
if (!schema.spec) {
throw new Error(`\`${TS_SCHEMA_PATH}\` needs to have a 'spec' export.`);
@@ -16,7 +16,11 @@
export const YAML_SCHEMA_PATH = 'src/schema/openapi.yaml';
export const TS_MODULE = 'src/schema/openapi.generated';
export const OUTPUT_PATH = 'src/schema/openapi/generated';
export const TS_MODULE = `${OUTPUT_PATH}/router`;
export const OLD_SCHEMA_PATH = `src/schema/openapi.generated.ts`;
export const TS_SCHEMA_PATH = `${TS_MODULE}.ts`;
@@ -24,8 +28,6 @@ export const GENERATOR_VERSION = `1.0.0`;
export const GENERATOR_NAME = 'typescript-backstage';
export const GENERATOR_FILE = `packages/template-openapi-plugin-client/generator/target/${GENERATOR_NAME}-openapi-generator-${GENERATOR_VERSION}.jar`;
export const OUTPUT_PATH = 'src/generated';
export const OPENAPI_IGNORE_FILES = [
// Get rid of the default files.
'*.md',
@@ -45,6 +47,7 @@ export const OPENAPI_IGNORE_FILES = [
// Override the created version.
'apis/*.ts',
'!apis/*.client.ts',
'!apis/*.server.ts',
'models/*.ts',
'!models/*.model.ts',
@@ -0,0 +1,40 @@
templateDir: templates/typescript-backstage-client
files:
api.mustache:
templateType: API
# For some reason, they check for destinationFilename differences. We have to change the ending to override the file.
destinationFilename: .client.ts
model.mustache:
templateType: Model
destinationFilename: .model.ts
modelGeneric.mustache:
templateType: SupportingFiles
modelOneOf.mustache:
templateType: SupportingFiles
modelGenericAdditionalProperties.mustache:
templateType: SupportingFiles
modelGenericEnums.mustache:
templateType: SupportingFiles
modelAlias.mustache:
templateType: SupportingFiles
modelEnum.mustache:
templateType: SupportingFiles
modelTaggedUnion.mustache:
templateType: SupportingFiles
models/models_all.mustache:
templateType: SupportingFiles
destinationFilename: models/index.ts
types/fetch.ts:
destinationFilename: types/fetch.ts
types/discovery.ts:
destinationFilename: types/discovery.ts
apis/index.mustache:
templateType: SupportingFiles
destinationFilename: apis/index.ts
pluginId.mustache:
templateType: SupportingFiles
destinationFilename: pluginId.ts
index.mustache:
templateType: SupportingFiles
destinationFilename: index.ts
@@ -29,8 +29,43 @@ export interface RequestOptions {
token?: string;
}
{{#operation}}
/**
* @public
*/
export type {{#lambda.pascalcase}}{{nickname}}{{/lambda.pascalcase}} = {
{{#hasPathParams}}
path: {
{{#pathParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/pathParams}}
},
{{/hasPathParams}}
{{#hasBodyParam}}
{{#bodyParam}}
body: {{{dataType}}},
{{/bodyParam}}
{{/hasBodyParam}}
{{#hasQueryParams}}
query: {
{{#queryParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/queryParams}}
},
{{/hasQueryParams}}
{{#hasHeaderParams}}
header: {
{{#headerParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/headerParams}}
},
{{/hasHeaderParams}}
}
{{/operation}}
/**
* {{{description}}}{{^description}}no description{{/description}}
* @public
*/
export class {{classname}}Client {
private readonly discoveryApi: DiscoveryApi;
@@ -53,39 +88,12 @@ export class {{classname}}Client {
* {{&summary}}
{{/summary}}
{{#allParams}}
* @param {{paramName}} {{description}}
* @param {{paramName}} - {{description}}
{{/allParams}}
*/
public async {{nickname}}(
// @ts-ignore
request: {
{{#hasPathParams}}
path: {
{{#pathParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/pathParams}}
},
{{/hasPathParams}}
{{#hasBodyParam}}
{{#bodyParam}}
body: {{{dataType}}},
{{/bodyParam}}
{{/hasBodyParam}}
{{#hasQueryParams}}
query: {
{{#queryParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/queryParams}}
},
{{/hasQueryParams}}
{{#hasHeaderParams}}
header: {
{{#headerParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/headerParams}}
},
{{/hasHeaderParams}}
},
request: {{#lambda.pascalcase}}{{nickname}}{{/lambda.pascalcase}},
options?: RequestOptions
): Promise<TypedResponse<{{{returnType}}} {{^returnType}}void{{/returnType}}>> {
const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
@@ -1,3 +1,6 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelAlias.mustache }}
/**
* @public
*/
export type {{classname}} = {{dataType}};
@@ -1,6 +1,9 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelEnum.mustache }}
{{#stringEnums}}
/**
* @public
*/
export enum {{classname}} {
{{#allowableValues}}
{{#enumVars}}
@@ -10,8 +13,14 @@ export enum {{classname}} {
}
{{/stringEnums}}
{{^stringEnums}}
/**
* @public
*/
export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}};
/**
* @public
*/
export const {{classname}} = {
{{#allowableValues}}
{{#enumVars}}
@@ -3,13 +3,14 @@
{{#models}}
{{#model}}
{{#description}}
/**
{{#description}}
* {{{.}}}
*/
{{/description}}
* @public
*/
{{^isEnum}}
export interface {{classname}} {
export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} {{classname}} {{#isNullable}}={{/isNullable}} {
{{>modelGenericAdditionalProperties}}
{{#vars}}
{{#description}}
@@ -19,12 +20,15 @@ export interface {{classname}} {
{{/description}}
'{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{/vars}}
}
}{{#isNullable}} | null{{/isNullable}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
/**
* @public
*/
export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{/vars}}
@@ -32,6 +36,9 @@ export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {
{{/hasEnums}}
{{/isEnum}}
{{#isEnum}}
/**
* @public
*/
export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{/model}}
@@ -3,11 +3,17 @@
{{#hasEnums}}
{{^stringEnums}}
/**
* @public
*/
export namespace {{classname}} {
{{/stringEnums}}
{{#vars}}
{{#isEnum}}
{{#stringEnums}}
/**
* @public
*/
export enum {{classname}}{{enumName}} {
{{#allowableValues}}
{{#enumVars}}
@@ -17,7 +23,14 @@ export enum {{classname}}{{enumName}} {
};
{{/stringEnums}}
{{^stringEnums}}
/**
* @public
*/
export type {{enumName}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}};
/**
* @public
*/
export const {{enumName}} = {
{{#allowableValues}}
{{#enumVars}}
@@ -8,9 +8,10 @@ import {
} from './';
{{/hasImports}}
{{#description}}
/**
{{#description}}
* {{{.}}}
*/
{{/description}}
* @public
*/
export type {{classname}} = {{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}};
@@ -1,10 +1,16 @@
templateDir: templates/typescript-backstage
templateDir: templates/typescript-backstage-server
files:
api.mustache:
templateType: API
# For some reason, they check for destinationFilename differences. We have to change the ending to override the file.
destinationFilename: .client.ts
destinationFilename: .server.ts
apis/index.mustache:
templateType: SupportingFiles
destinationFilename: apis/index.ts
index.mustache:
templateType: SupportingFiles
destinationFilename: index.ts
model.mustache:
templateType: Model
destinationFilename: .model.ts
@@ -24,12 +30,4 @@ files:
templateType: SupportingFiles
models/models_all.mustache:
templateType: SupportingFiles
destinationFilename: models/index.ts
types/fetch.ts: {}
types/discovery.ts: {}
apis/index.mustache:
templateType: SupportingFiles
destinationFilename: apis/index.ts
pluginId.mustache:
templateType: SupportingFiles
destinationFilename: pluginId.ts
destinationFilename: models/index.ts
@@ -0,0 +1,54 @@
{{>licenseInfo}}
{{#imports}}
import { {{classname}} } from '{{filename}}.model{{importFileExtension}}';
{{/imports}}
{{#operations}}
{{#operation}}
/**
* @public
*/
export type {{#lambda.pascalcase}}{{nickname}}{{/lambda.pascalcase}} = {
{{#hasPathParams}}
path: {
{{#pathParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/pathParams}}
},
{{/hasPathParams}}
{{#hasBodyParam}}
{{#bodyParam}}
body: {{{dataType}}},
{{/bodyParam}}
{{/hasBodyParam}}
{{#hasQueryParams}}
query: {
{{#queryParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/queryParams}}
},
{{/hasQueryParams}}
{{#hasHeaderParams}}
header: {
{{#headerParams}}
{{paramName}}{{^required}}?{{/required}}: {{{dataType}}},
{{/headerParams}}
},
{{/hasHeaderParams}}
response: {{#responses}} {{{dataType}}}{{^dataType}}void{{/dataType}} {{^-last}} | {{/-last}} {{/responses}}
}
{{/operation}}
/**
* {{{description}}}{{^description}}no description{{/description}}
*/
export type EndpointMap = {
{{#operation}}
'#{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}|{{path}}': {{#lambda.pascalcase}}{{nickname}}{{/lambda.pascalcase}},
{{/operation}}
}
{{/operations}}
@@ -0,0 +1,3 @@
//
export * from './DefaultApi.server';
@@ -0,0 +1,4 @@
//
export * from './apis';
export * from './router';
@@ -0,0 +1,5 @@
//
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
@@ -0,0 +1,11 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/model.mustache#L17. }}
{{>licenseInfo}}
{{#models}}
{{#model}}
{{#tsImports}}
import { {{classname}} } from '{{filename}}.model';
{{/tsImports}}
{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#isAlias}}{{>modelAlias}}{{/isAlias}}{{^isAlias}}{{#taggedUnions}}{{>modelTaggedUnion}}{{/taggedUnions}}{{^taggedUnions}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/taggedUnions}}{{/isAlias}}{{/isEnum}}
{{/model}}
{{/models}}
@@ -0,0 +1,6 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelAlias.mustache }}
/**
* @public
*/
export type {{classname}} = {{dataType}};
@@ -0,0 +1,31 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelEnum.mustache }}
{{#stringEnums}}
/**
* @public
*/
export enum {{classname}} {
{{#allowableValues}}
{{#enumVars}}
{{name}} = {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
}
{{/stringEnums}}
{{^stringEnums}}
/**
* @public
*/
export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}};
/**
* @public
*/
export const {{classname}} = {
{{#allowableValues}}
{{#enumVars}}
{{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
};
{{/stringEnums}}
@@ -0,0 +1,45 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache }}
{{#models}}
{{#model}}
/**
{{#description}}
* {{{.}}}
{{/description}}
* @public
*/
{{^isEnum}}
export {{#isNullable}}type{{/isNullable}}{{^isNullable}}interface{{/isNullable}} {{classname}} {{#isNullable}}={{/isNullable}} {
{{>modelGenericAdditionalProperties}}
{{#vars}}
{{#description}}
/**
* {{{.}}}
*/
{{/description}}
'{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{/vars}}
}{{#isNullable}} | null{{/isNullable}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
/**
* @public
*/
export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
{{/isEnum}}
{{#isEnum}}
/**
* @public
*/
export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}};
{{/isEnum}}
{{/model}}
{{/models}}
@@ -0,0 +1,5 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericAdditionalProperties.mustache }}
{{#additionalPropertiesType}}
[key: string]: {{{additionalPropertiesType}}};
{{/additionalPropertiesType}}
@@ -0,0 +1,45 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericEnums.mustache }}
{{#hasEnums}}
{{^stringEnums}}
/**
* @public
*/
export namespace {{classname}} {
{{/stringEnums}}
{{#vars}}
{{#isEnum}}
{{#stringEnums}}
/**
* @public
*/
export enum {{classname}}{{enumName}} {
{{#allowableValues}}
{{#enumVars}}
{{name}} = {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
};
{{/stringEnums}}
{{^stringEnums}}
/**
* @public
*/
export type {{enumName}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}};
/**
* @public
*/
export const {{enumName}} = {
{{#allowableValues}}
{{#enumVars}}
{{name}}: {{{value}}} as {{enumName}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
};
{{/stringEnums}}
{{/isEnum}}
{{/vars}}
{{^stringEnums}}}{{/stringEnums}}
{{/hasEnums}}
@@ -0,0 +1,17 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelOneOf.mustache }}
{{#hasImports}}
import {
{{#imports}}
{{{.}}},
{{/imports}}
} from './';
{{/hasImports}}
/**
{{#description}}
* {{{.}}}
{{/description}}
* @public
*/
export type {{classname}} = {{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}};
@@ -0,0 +1,23 @@
{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache }}
{{#discriminator}}
export type {{classname}} = {{#children}}{{^-first}} | {{/-first}}{{classname}}{{/children}};
{{/discriminator}}
{{^discriminator}}
{{#parent}}
export interface {{classname}} { {{>modelGenericAdditionalProperties}}
{{#allVars}}
{{#description}}
/**
* {{{.}}}
*/
{{/description}}
{{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}};
{{/allVars}}
}
{{>modelGenericEnums}}
{{/parent}}
{{^parent}}
{{>modelGeneric}}
{{/parent}}
{{/discriminator}}
@@ -0,0 +1,7 @@
//
{{#models}}
{{#model}}
export * from '{{{ importPath }}}.model{{importFileExtension}}'
{{/model}}
{{/models}}
@@ -0,0 +1,229 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model';
import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model';
import { CreateLocation201Response } from '../models/CreateLocation201Response.model';
import { CreateLocationRequest } from '../models/CreateLocationRequest.model';
import { EntitiesBatchResponse } from '../models/EntitiesBatchResponse.model';
import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model';
import { Entity } from '../models/Entity.model';
import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model';
import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model';
import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model';
import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model';
import { Location } from '../models/Location.model';
import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model';
import { ValidateEntity400Response } from '../models/ValidateEntity400Response.model';
import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model';
/**
* @public
*/
export type AnalyzeLocation = {
body: AnalyzeLocationRequest;
response: AnalyzeLocationResponse | Error | Error;
};
/**
* @public
*/
export type CreateLocation = {
body: CreateLocationRequest;
query: {
dryRun?: string;
};
response: CreateLocation201Response | Error | Error;
};
/**
* @public
*/
export type DeleteEntityByUid = {
path: {
uid: string;
};
response: void | Error | Error;
};
/**
* @public
*/
export type DeleteLocation = {
path: {
id: string;
};
response: void | Error | Error;
};
/**
* @public
*/
export type GetEntities = {
query: {
fields?: Array<string>;
limit?: number;
filter?: Array<string>;
offset?: number;
after?: string;
order?: Array<string>;
};
response: Array<Entity> | Error | Error;
};
/**
* @public
*/
export type GetEntitiesByQuery = {
query: {
fields?: Array<string>;
limit?: number;
offset?: number;
orderField?: Array<string>;
cursor?: string;
filter?: Array<string>;
fullTextFilterTerm?: string;
fullTextFilterFields?: Array<string>;
};
response: EntitiesQueryResponse | Error | Error;
};
/**
* @public
*/
export type GetEntitiesByRefs = {
body: GetEntitiesByRefsRequest;
query: {
filter?: Array<string>;
};
response: EntitiesBatchResponse | Error | Error;
};
/**
* @public
*/
export type GetEntityAncestryByName = {
path: {
kind: string;
namespace: string;
name: string;
};
response: EntityAncestryResponse | Error | Error;
};
/**
* @public
*/
export type GetEntityByName = {
path: {
kind: string;
namespace: string;
name: string;
};
response: Entity | Error | Error;
};
/**
* @public
*/
export type GetEntityByUid = {
path: {
uid: string;
};
response: Entity | Error | Error;
};
/**
* @public
*/
export type GetEntityFacets = {
query: {
facet: Array<string>;
filter?: Array<string>;
};
response: EntityFacetsResponse | Error | Error;
};
/**
* @public
*/
export type GetLocation = {
path: {
id: string;
};
response: Location | Error;
};
/**
* @public
*/
export type GetLocationByEntity = {
path: {
kind: string;
namespace: string;
name: string;
};
response: Location | Error;
};
/**
* @public
*/
export type GetLocations = {
response: Array<GetLocations200ResponseInner> | Error;
};
/**
* @public
*/
export type RefreshEntity = {
body: RefreshEntityRequest;
response: void | Error | Error;
};
/**
* @public
*/
export type ValidateEntity = {
body: ValidateEntityRequest;
response: void | ValidateEntity400Response;
};
/**
* no description
*/
export type EndpointMap = {
'#post|/analyze-location': AnalyzeLocation;
'#post|/locations': CreateLocation;
'#_delete|/entities/by-uid/{uid}': DeleteEntityByUid;
'#_delete|/locations/{id}': DeleteLocation;
'#get|/entities': GetEntities;
'#get|/entities/by-query': GetEntitiesByQuery;
'#post|/entities/by-refs': GetEntitiesByRefs;
'#get|/entities/by-name/{kind}/{namespace}/{name}/ancestry': GetEntityAncestryByName;
'#get|/entities/by-name/{kind}/{namespace}/{name}': GetEntityByName;
'#get|/entities/by-uid/{uid}': GetEntityByUid;
'#get|/entity-facets': GetEntityFacets;
'#get|/locations/{id}': GetLocation;
'#get|/locations/by-entity/{kind}/{namespace}/{name}': GetLocationByEntity;
'#get|/locations': GetLocations;
'#post|/refresh': RefreshEntity;
'#post|/validate-entity': ValidateEntity;
};
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './DefaultApi.server';
@@ -0,0 +1,18 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './apis';
export * from './router';
@@ -0,0 +1,46 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface AnalyzeLocationEntityField {
/**
* A text to show to the user to inform about the choices made. Like, it could say \"Found a CODEOWNERS file that covers this target, so we suggest leaving this field empty; which would currently make it owned by X\" where X is taken from the codeowners file.
*/
description: string;
value: string | null;
/**
* The outcome of the analysis for this particular field
*/
state: AnalyzeLocationEntityFieldStateEnum;
/**
* e.g. \"spec.owner\"? The frontend needs to know how to \"inject\" the field into the entity again if the user wants to change it
*/
field: string;
}
/**
* @public
*/
export type AnalyzeLocationEntityFieldStateEnum =
| 'analysisSuggestedValue'
| 'analysisSuggestedNoValue'
| 'needsUserInput';
@@ -0,0 +1,31 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { Entity } from '../models/Entity.model';
import { LocationSpec } from '../models/LocationSpec.model';
/**
* If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already
* @public
*/
export interface AnalyzeLocationExistingEntity {
entity: Entity;
isRegistered: boolean;
location: LocationSpec;
}
@@ -0,0 +1,30 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model';
import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model';
/**
* This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
* @public
*/
export interface AnalyzeLocationGenerateEntity {
fields: Array<AnalyzeLocationEntityField>;
entity: RecursivePartialEntity;
}
@@ -0,0 +1,28 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
import { LocationInput } from '../models/LocationInput.model';
/**
* @public
*/
export interface AnalyzeLocationRequest {
catalogFileName?: string;
location: LocationInput;
}

Some files were not shown because too many files have changed in this diff Show More