From 21b66b184dc88f744c69ead1744f4b4d5de4ff0a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Jul 2024 12:37:43 +0200 Subject: [PATCH 1/8] chore: initial experiments Signed-off-by: blam wip Signed-off-by: blam wip2 Signed-off-by: blam wip3 Signed-off-by: blam wip3 Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 41 ++++++--- .../src/extensions/IconBundleBlueprint.ts | 36 ++++++++ .../src/wiring/createExtension.ts | 18 +++- .../src/wiring/createExtensionBlueprint.ts | 92 ++++++++++++++----- .../src/app/createExtensionTester.tsx | 4 +- plugins/catalog-graph/api-report-alpha.md | 72 +++++++++++++++ plugins/catalog-graph/src/alpha.tsx | 79 ++++++++++++---- plugins/catalog-react/api-report-alpha.md | 56 +++++++++-- plugins/catalog-react/src/alpha.tsx | 51 ++++++++++ plugins/catalog/api-report-alpha.md | 35 ++++++- plugins/catalog/src/alpha/index.ts | 2 + plugins/search-react/api-report-alpha.md | 3 +- plugins/search/api-report-alpha.md | 26 ++++-- plugins/techdocs/api-report-alpha.md | 18 ++-- plugins/user-settings/api-report-alpha.md | 10 +- 15 files changed, 456 insertions(+), 87 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index d6a91c0c99..0c7c3b1d92 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -89,6 +89,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; +import { ZodObject } from 'zod'; import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; @@ -391,7 +392,7 @@ export function createApiExtension< configSchema?: PortableSchema; inputs?: TInputs; }, -): ExtensionDefinition; +): ExtensionDefinition>; // @public (undocumented) export namespace createApiExtension { @@ -487,7 +488,7 @@ export function createComponentExtension< inputs: Expand>; }) => ComponentType; }; -}): ExtensionDefinition; +}): ExtensionDefinition>; // @public (undocumented) export namespace createComponentExtension { @@ -511,10 +512,11 @@ export function createComponentRef(options: { export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, - TConfig = never, + TConfigSchema extends z.ZodObject, + TConfig extends z.infer, >( - options: CreateExtensionOptions, -): ExtensionDefinition; + options: CreateExtensionOptions, +): ExtensionDefinition; // @public export function createExtensionBlueprint< @@ -605,7 +607,8 @@ export function createExtensionInput< export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, - TConfig, + TConfigSchema extends z.ZodObject, + TConfig extends z.infer, > { // (undocumented) attachTo: { @@ -613,6 +616,10 @@ export interface CreateExtensionOptions< input: string; }; // (undocumented) + config?: { + schema: (context: { z: typeof z }) => TConfigSchema; + }; + // @deprecated (undocumented) configSchema?: PortableSchema; // (undocumented) disabled?: boolean; @@ -672,9 +679,12 @@ export function createNavItemExtension(options: { routeRef: RouteRef; title: string; icon: IconComponent_2; -}): ExtensionDefinition<{ - title: string; -}>; +}): ExtensionDefinition< + { + title: string; + }, + ZodObject +>; // @public (undocumented) export namespace createNavItemExtension { @@ -696,7 +706,7 @@ export function createNavLogoExtension(options: { namespace?: string; logoIcon: JSX.Element; logoFull: JSX.Element; -}): ExtensionDefinition; +}): ExtensionDefinition>; // @public (undocumented) export namespace createNavLogoExtension { @@ -851,7 +861,7 @@ export function createSubRouteRef< // @public (undocumented) export function createThemeExtension( theme: AppTheme, -): ExtensionDefinition; +): ExtensionDefinition>; // @public (undocumented) export namespace createThemeExtension { @@ -867,7 +877,7 @@ export namespace createThemeExtension { export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition; +}): ExtensionDefinition>; // @public (undocumented) export namespace createTranslationExtension { @@ -1011,7 +1021,10 @@ export type ExtensionDataValues = { }; // @public (undocumented) -export interface ExtensionDefinition { +export interface ExtensionDefinition< + TConfig, + TConfigSchema extends z.ZodObject = any, +> { // (undocumented) $$type: '@backstage/ExtensionDefinition'; // (undocumented) @@ -1022,6 +1035,8 @@ export interface ExtensionDefinition { // (undocumented) readonly configSchema?: PortableSchema; // (undocumented) + readonly configSchema2?: TConfigSchema; + // (undocumented) readonly disabled: boolean; // (undocumented) readonly kind?: string; diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts index 2ad1404102..aba3303f2f 100644 --- a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts @@ -33,3 +33,39 @@ export const IconBundleBlueprint = createExtensionBlueprint({ icons: iconsDataRef, }, }); + +IconBundleBlueprint.make({ + name: 'asd', + params: { + icons: { + 'icon-name': () => null, + }, + }, +}); + +IconBundleBlueprint.make({ + name: 'asd', + params: { + icons: { + 'icon-name': () => null, + }, + }, +}); + +IconBundleBlueprint.make({ + name: 'asd', + factory() { + return { + icons: { + 'icon-name': () => null, + }, + }; + }, +}); + +IconBundleBlueprint.make({ + name: 'asd', + factory(f) { + return f({ icons: { 'icon-name': () => null } }); + }, +}); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index e87f3a638e..3733e44957 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -19,7 +19,7 @@ import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ExtensionDataRef } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; - +import { z } from 'zod'; /** @public */ export type AnyExtensionDataMap = { [name in string]: ExtensionDataRef; @@ -82,6 +82,7 @@ export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, > { kind?: string; namespace?: string; @@ -90,7 +91,11 @@ export interface CreateExtensionOptions< disabled?: boolean; inputs?: TInputs; output: TOutput; + /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; + config?: { + schema: TConfigSchema; + }; factory(context: { node: AppNode; config: TConfig; @@ -99,7 +104,7 @@ export interface CreateExtensionOptions< } /** @public */ -export interface ExtensionDefinition { +export interface ExtensionDefinition { $$type: '@backstage/ExtensionDefinition'; readonly kind?: string; readonly namespace?: string; @@ -107,6 +112,9 @@ export interface ExtensionDefinition { readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; + readonly config?: { + schema: TConfigSchema; + }; } /** @internal */ @@ -144,9 +152,10 @@ export function toInternalExtensionDefinition( export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, - TConfig = never, + TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, >( - options: CreateExtensionOptions, + options: CreateExtensionOptions, ): ExtensionDefinition { return { $$type: '@backstage/ExtensionDefinition', @@ -159,6 +168,7 @@ export function createExtension< inputs: options.inputs ?? {}, output: options.output, configSchema: options.configSchema, + config: options.config, factory({ inputs, ...rest }) { // TODO: Simplify this, but TS wouldn't infer the input type for some reason return options.factory({ diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index fb9081f7f8..61227c7dc1 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -15,7 +15,6 @@ */ import { AppNode } from '../apis'; -import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { AnyExtensionDataMap, @@ -25,6 +24,7 @@ import { ResolvedExtensionInputs, createExtension, } from './createExtension'; +import { z } from 'zod'; /** * @public @@ -33,7 +33,7 @@ export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { kind: string; @@ -42,12 +42,16 @@ export interface CreateExtensionBlueprintOptions< disabled?: boolean; inputs?: TInputs; output: TOutput; - configSchema?: PortableSchema; + config?: { + schema: TConfigSchema; + }; factory( params: TParams, context: { node: AppNode; - config: TConfig; + config: { + [key in keyof TConfigSchema]: z.infer>; + }; inputs: Expand>; }, ): Expand>; @@ -62,7 +66,7 @@ export interface ExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { dataRefs: TDataRefs; @@ -73,7 +77,11 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make( + make< + TExtensionConfigSchema extends { + [key: string]: (zImpl: typeof z) => z.ZodTypeAny; + }, + >( args: { namespace?: string; name?: string; @@ -81,20 +89,30 @@ export interface ExtensionBlueprint< disabled?: boolean; inputs?: TInputs; output?: TOutput; - configSchema?: PortableSchema; + config?: { + schema: TExtensionConfigSchema; + }; } & ( | { factory( originalFactory: ( params: TParams, context?: { - config?: TConfig; + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: TConfig; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; inputs: Expand>; }, ): Expand>; @@ -103,7 +121,14 @@ export interface ExtensionBlueprint< params: TParams; } ), - ): ExtensionDefinition; + ): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }, + TExtensionConfigSchema + >; } /** @@ -113,7 +138,7 @@ class ExtensionBlueprintImpl< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { constructor( @@ -121,7 +146,7 @@ class ExtensionBlueprintImpl< TParams, TInputs, TOutput, - TConfig, + TConfigSchema, TDataRefs >, ) { @@ -130,30 +155,49 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; - public make(args: { + public make< + TExtensionConfigSchema extends TConfigSchema = TConfigSchema, + >(args: { namespace?: string; name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; output?: TOutput; - configSchema?: PortableSchema; params?: TParams; + config?: { + schema: TConfigSchema; + }; factory?( originalFactory: ( params: TParams, context?: { - config?: TConfig; + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: TConfig; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; inputs: Expand>; }, ): Expand>; - }): ExtensionDefinition { + }): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }, + TExtensionConfigSchema + > { return createExtension({ kind: this.options.kind, namespace: args.namespace ?? this.options.namespace, @@ -162,14 +206,18 @@ class ExtensionBlueprintImpl< disabled: args.disabled ?? this.options.disabled, inputs: args.inputs ?? this.options.inputs, output: args.output ?? this.options.output, - configSchema: args.configSchema ?? this.options.configSchema, // TODO: some config merging or smth + config: args.config, factory: ({ node, config, inputs }) => { if (args.factory) { return args.factory( ( innerParams: TParams, innerContext?: { - config?: TConfig; + config?: { + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; + }; inputs?: Expand>; }, ) => @@ -207,16 +255,16 @@ export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< TParams, TInputs, TOutput, - TConfig, + TConfigSchema, TDataRefs >, -): ExtensionBlueprint { +): ExtensionBlueprint { return new ExtensionBlueprintImpl(options); } diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index a98879fbba..d6be346002 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -102,11 +102,11 @@ export class ExtensionTester { ...output, path: coreExtensionData.routePath, }, - factory: params => ({ + factory: (params: any) => ({ ...factory(params), path: '/', }), - }); + } as any); tester.add(extension, options); return tester; } diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 3895f12fcf..afd03965ae 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -4,8 +4,76 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { objectUtil } from 'zod'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ZodArray } from 'zod'; +import { ZodBoolean } from 'zod'; +import { ZodEnum } from 'zod'; +import { ZodNativeEnum } from 'zod'; +import { ZodNumber } from 'zod'; +import { ZodOptional } from 'zod'; +import { ZodString } from 'zod'; +import { ZodTuple } from 'zod'; + +// Warning: (ae-missing-release-tag) "CatalogGraphEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const CatalogGraphEntityCard: ExtensionDefinition< + { + filter?: string | undefined; + height?: number | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + title?: string | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + }, + objectUtil.extendShape< + objectUtil.extendShape< + { + filter: ZodOptional; + }, + { + title: ZodOptional; + height: ZodOptional; + } + >, + { + kinds: ZodOptional>; + relations: ZodOptional>; + maxDepth: ZodOptional; + unidirectional: ZodOptional; + mergeRelations: ZodOptional; + direction: ZodOptional>; + relationPairs: ZodOptional< + ZodArray, 'many'> + >; + zoom: ZodOptional>; + curve: ZodOptional>; + } + >, + { + filter?: string | undefined; + height?: number | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + title?: string | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + } +>; // @public (undocumented) const _default: BackstagePlugin< @@ -25,5 +93,9 @@ const _default: BackstagePlugin< >; export default _default; +// Warnings were encountered during analysis: +// +// src/alpha.d.ts:6:5 - (ae-forgotten-export) The symbol "Direction" needs to be exported by the entry point alpha.d.ts + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 6d8429332e..cf663406fa 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -24,7 +24,7 @@ import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; import { Direction } from './components'; @@ -46,24 +46,67 @@ function getEntityGraphRelationsConfigSchema( }); } -const CatalogGraphEntityCard = createEntityCardExtension({ +export const CatalogGraphEntityCard = EntityCardBlueprint.make({ name: 'relations', - configSchema: createSchemaFromZod(z => - z - .object({ - // Filter is a config required to all entity cards - filter: z.string().optional(), - title: z.string().optional(), - height: z.number().optional(), - // Skipping a "variant" config for now, defaulting to "gridItem" in the component - // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - }) - .merge(getEntityGraphRelationsConfigSchema(z)), - ), - loader: async ({ config: { filter, ...props } }) => - import('./components/CatalogGraphCard').then(m => - compatWrapper(), - ), + // configSchema: origSchema => + // createSchemaFromZod(z => + // z + // .object({ + // // Filter is a config required to all entity cards + // // filter: z.string().optional(), + // title: z.string().optional(), + // height: z.number().optional(), + // // Skipping a "variant" config for now, defaulting to "gridItem" in the component + // // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + // }) + // .merge(getEntityGraphRelationsConfigSchema(z)) + // .merge(origSchema), + // ), + + // config: { + // schema: ({ z, parent }) => + // parent + // .merge({ + // title: z.string().optional(), + // height: z.number().optional(), + // }) + // .merge(getEntityGraphRelationsConfigSchema(z)), + // }, + + // config: { + // schema: ({ z, parent }) => + // parent + // .extend({ + // title: z.string().optional(), + // height: z.number().optional(), + // }) + // .merge(getEntityGraphRelationsConfigSchema(z)), + // // .merge(parent) + // }, + + // TODO: implement this and check the types. 👋 zod types. + config: { + schema: { + title: z => z.string().optional(), + height: z => z.number().optional(), + }, + }, + + // config: { + // schema: z => ({ + // title: z.string().optional(), + // height: z.number().optional(), + // }), + // }, + + factory(origFactory, { config: { filter: _, ...props } }) { + return origFactory({ + loader: async () => + import('./components/CatalogGraphCard').then(m => + compatWrapper(), + ), + }); + }, }); const CatalogGraphPage = createPageExtension({ diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index d2bfb29458..ccf8205f59 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -3,17 +3,20 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { ZodOptional } from 'zod'; +import { ZodRawShape } from 'zod'; +import { ZodString } from 'zod'; // @alpha (undocumented) export const catalogExtensionData: { @@ -115,7 +118,7 @@ export function createEntityCardExtension< config: TConfig; inputs: Expand>; }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) export function createEntityContentExtension< @@ -138,11 +141,48 @@ export function createEntityContentExtension< loader: (options: { inputs: Expand>; }) => Promise; -}): ExtensionDefinition<{ - title: string; - path: string; - filter?: string | undefined; -}>; +}): ExtensionDefinition< + { + title: string; + path: string; + filter?: string | undefined; + }, + ZodRawShape +>; + +// @alpha (undocumented) +export const EntityCardBlueprint: ExtensionBlueprint< + { + filter?: string | ((entity: Entity) => boolean) | undefined; + loader: () => Promise; + }, + AnyExtensionInputMap, + { + element: ConfigurableExtensionDataRef< + 'core.reactElement', + React_2.JSX.Element, + {} + >; + filterFunction: ConfigurableExtensionDataRef< + 'catalog.entity-filter-function', + (entity: Entity) => boolean, + ((entity: Entity) => boolean) & { + optional: true; + } + >; + filterExpression: ConfigurableExtensionDataRef< + 'catalog.entity-filter-expression', + string, + string & { + optional: true; + } + >; + }, + { + filter: ZodOptional; + }, + never +>; // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx index e81b8e5ebf..f397c76ce0 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha.tsx @@ -22,6 +22,7 @@ import { RouteRef, coreExtensionData, createExtension, + createExtensionBlueprint, createExtensionDataRef, createSchemaFromZod, } from '@backstage/frontend-plugin-api'; @@ -47,6 +48,49 @@ export const catalogExtensionData = { }), }; +/** @alpha */ +export const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, + // configSchema: createSchemaFromZod(z => + // z.object({ + // filter: z.string().optional(), + // }), + // ), + config: { + schema: { + filter: z => z.string().optional(), + }, + }, + output: { + element: coreExtensionData.reactElement, + filterFunction: catalogExtensionData.entityFilterFunction.optional(), + filterExpression: catalogExtensionData.entityFilterExpression.optional(), + }, + factory( + params: { + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; + loader: () => Promise; + }, + { node, config }, + ) { + const ExtensionComponent = lazy(() => + params.loader().then(element => ({ default: () => element })), + ); + + return { + element: ( + + + + ), + ...mergeFilters({ config, options: params }), + }; + }, +}); + // TODO: Figure out how to merge with provided config schema /** @alpha */ export function createEntityCardExtension< @@ -154,6 +198,13 @@ export function createEntityContentExtension< filter: z.string().optional(), }), ), + // config: { + // schema: ({ z }) => ({ + // path: z.string().default(options.defaultPath), + // title: z.string().default(options.defaultTitle), + // filter: z.string().optional(), + // }), + // }, factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 102e2a7aa3..03711b7d3d 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -12,6 +12,39 @@ import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { ZodObject } from 'zod'; + +// Warning: (ae-missing-release-tag) "catalogAboutEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const catalogAboutEntityCard: ExtensionDefinition< + { + filter?: string | undefined; + }, + ZodObject +>; + +// Warning: (ae-missing-release-tag) "catalogLinksEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const catalogLinksEntityCard: ExtensionDefinition< + { + filter?: string | undefined; + }, + ZodObject +>; + +// Warning: (ae-missing-release-tag) "catalogOverviewEntityContent" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const catalogOverviewEntityContent: ExtensionDefinition< + { + title: string; + path: string; + filter?: string | undefined; + }, + ZodObject +>; // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< @@ -106,7 +139,7 @@ export function createCatalogFilterExtension< inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition>; // @alpha (undocumented) const _default: BackstagePlugin< diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index 06a78ec6ea..acf61865ba 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -16,3 +16,5 @@ export { default } from './plugin'; export { createCatalogFilterExtension } from './createCatalogFilterExtension'; +export { catalogAboutEntityCard, catalogLinksEntityCard } from './entityCards'; +export { catalogOverviewEntityContent } from './entityContents'; diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index aea85d0f81..6160759042 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -11,6 +11,7 @@ import { ListItemProps } from '@material-ui/core/ListItem'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; +import { ZodObject } from 'zod'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { @@ -25,7 +26,7 @@ export function createSearchResultListItemExtension< }, >( options: SearchResultItemExtensionOptions, -): ExtensionDefinition; +): ExtensionDefinition>; // @alpha (undocumented) export namespace createSearchResultListItemExtension { diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index db2999ba25..ade5f530ef 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -6,6 +6,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -17,18 +18,27 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const searchApi: ExtensionDefinition<{}>; +export const searchApi: ExtensionDefinition< + {}, + ZodObject +>; // @alpha (undocumented) -export const searchNavItem: ExtensionDefinition<{ - title: string; -}>; +export const searchNavItem: ExtensionDefinition< + { + title: string; + }, + ZodObject +>; // @alpha (undocumented) -export const searchPage: ExtensionDefinition<{ - path: string; - noTrack: boolean; -}>; +export const searchPage: ExtensionDefinition< + { + path: string; + noTrack: boolean; + }, + any +>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index cf9f5fa9a3..fc57370a53 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -6,6 +6,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -23,13 +24,16 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ - lineClamp: number; - noTrack: boolean; - asListItem: boolean; - asLink: boolean; - title?: string | undefined; -}>; +export const techDocsSearchResultListItemExtension: ExtensionDefinition< + { + lineClamp: number; + noTrack: boolean; + asListItem: boolean; + asLink: boolean; + title?: string | undefined; + }, + ZodObject +>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index b06fd1e583..db4373fd43 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -7,6 +7,7 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -18,9 +19,12 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const settingsNavItem: ExtensionDefinition<{ - title: string; -}>; +export const settingsNavItem: ExtensionDefinition< + { + title: string; + }, + ZodObject +>; // @alpha (undocumented) export const userSettingsTranslationRef: TranslationRef< From 8e387301a4f27c17aa631005a8ec6634f495965b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 10:56:12 +0200 Subject: [PATCH 2/8] chore: moving things around Signed-off-by: blam --- .../src/wiring/createExtension.ts | 13 +- .../src/wiring/createExtensionBlueprint.ts | 125 +++++++++++------- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 3733e44957..61223ca8e8 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -82,7 +82,7 @@ export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, > { kind?: string; namespace?: string; @@ -94,7 +94,9 @@ export interface CreateExtensionOptions< /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; config?: { - schema: TConfigSchema; + schema: { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + }; }; factory(context: { node: AppNode; @@ -104,7 +106,10 @@ export interface CreateExtensionOptions< } /** @public */ -export interface ExtensionDefinition { +export interface ExtensionDefinition< + TConfig, + TConfigSchema = never /* TODO -> refactor */, +> { $$type: '@backstage/ExtensionDefinition'; readonly kind?: string; readonly namespace?: string; @@ -153,7 +158,7 @@ export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, >( options: CreateExtensionOptions, ): ExtensionDefinition { diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 61227c7dc1..9cd5689a95 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -33,7 +33,7 @@ export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { kind: string; @@ -43,15 +43,17 @@ export interface CreateExtensionBlueprintOptions< inputs?: TInputs; output: TOutput; config?: { - schema: TConfigSchema; + schema: { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + }; }; factory( params: TParams, context: { node: AppNode; - config: { - [key in keyof TConfigSchema]: z.infer>; - }; + config: Expand<{ + [key in keyof TConfigSchema]: z.infer; + }>; inputs: Expand>; }, ): Expand>; @@ -66,7 +68,7 @@ export interface ExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { dataRefs: TDataRefs; @@ -77,11 +79,7 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make< - TExtensionConfigSchema extends { - [key: string]: (zImpl: typeof z) => z.ZodTypeAny; - }, - >( + make( args: { namespace?: string; name?: string; @@ -90,7 +88,11 @@ export interface ExtensionBlueprint< inputs?: TInputs; output?: TOutput; config?: { - schema: TExtensionConfigSchema; + schema: { + [key in keyof TExtensionConfigSchema]: ( + zImpl: typeof z, + ) => TExtensionConfigSchema[key]; + }; }; } & ( | { @@ -98,21 +100,23 @@ export interface ExtensionBlueprint< originalFactory: ( params: TParams, context?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; + config?: Expand<{ + [key in keyof TConfigSchema]: z.infer; + }>; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; + config: Expand< + { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer; + } + >; inputs: Expand>; }, ): Expand>; @@ -124,7 +128,7 @@ export interface ExtensionBlueprint< ): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType + TExtensionConfigSchema[key] >; }, TExtensionConfigSchema @@ -138,7 +142,7 @@ class ExtensionBlueprintImpl< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { constructor( @@ -156,7 +160,7 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; public make< - TExtensionConfigSchema extends TConfigSchema = TConfigSchema, + TExtensionConfigSchema extends { [key: string]: z.ZodTypeAny }, >(args: { namespace?: string; name?: string; @@ -166,37 +170,45 @@ class ExtensionBlueprintImpl< output?: TOutput; params?: TParams; config?: { - schema: TConfigSchema; + schema: { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + }; }; factory?( originalFactory: ( params: TParams, context?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; + config?: Expand<{ + [key in keyof TConfigSchema]: z.infer; + }>; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }; + config: Expand< + { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer; + } + >; inputs: Expand>; }, ): Expand>; }): ExtensionDefinition< - { - [key in keyof TExtensionConfigSchema]: z.infer< - ReturnType - >; - }, - TExtensionConfigSchema + Expand< + { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer; + } + >, + Expand > { return createExtension({ kind: this.options.kind, @@ -206,18 +218,31 @@ class ExtensionBlueprintImpl< disabled: args.disabled ?? this.options.disabled, inputs: args.inputs ?? this.options.inputs, output: args.output ?? this.options.output, - config: args.config, + config: { + schema: { + ...this.options.config?.schema, + ...args.config, + } as Expand< + { + [key in keyof TConfigSchema]: ( + zImpl: typeof z, + ) => TConfigSchema[key]; + } & { + [key in keyof TExtensionConfigSchema]: ( + zImpl: typeof z, + ) => TExtensionConfigSchema[key]; + } + >, + }, factory: ({ node, config, inputs }) => { if (args.factory) { return args.factory( ( innerParams: TParams, innerContext?: { - config?: { - [key in keyof TConfigSchema]: z.infer< - ReturnType - >; - }; + config?: Expand<{ + [key in keyof TConfigSchema]: z.infer; + }>; inputs?: Expand>; }, ) => @@ -255,7 +280,7 @@ export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< @@ -266,5 +291,5 @@ export function createExtensionBlueprint< TDataRefs >, ): ExtensionBlueprint { - return new ExtensionBlueprintImpl(options); + return new ExtensionBlueprintImpl(options) as any; } From d22f1316c50da7ea5d767fd23d9c6acf9be9ba07 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 11:25:11 +0200 Subject: [PATCH 3/8] chore: getting somewhere with portable schemas and zod types Signed-off-by: blam --- .../src/wiring/createExtension.ts | 25 ++-- .../src/wiring/createExtensionBlueprint.ts | 120 ++++++++---------- plugins/catalog-graph/api-report-alpha.md | 67 +--------- 3 files changed, 75 insertions(+), 137 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 61223ca8e8..6fa3a50634 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -15,7 +15,7 @@ */ import { AppNode } from '../apis'; -import { PortableSchema } from '../schema'; +import { PortableSchema, createSchemaFromZod } from '../schema'; import { Expand } from '../types'; import { ExtensionDataRef } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; @@ -106,10 +106,7 @@ export interface CreateExtensionOptions< } /** @public */ -export interface ExtensionDefinition< - TConfig, - TConfigSchema = never /* TODO -> refactor */, -> { +export interface ExtensionDefinition { $$type: '@backstage/ExtensionDefinition'; readonly kind?: string; readonly namespace?: string; @@ -117,9 +114,6 @@ export interface ExtensionDefinition< readonly attachTo: { id: string; input: string }; readonly disabled: boolean; readonly configSchema?: PortableSchema; - readonly config?: { - schema: TConfigSchema; - }; } /** @internal */ @@ -162,6 +156,18 @@ export function createExtension< >( options: CreateExtensionOptions, ): ExtensionDefinition { + // TODO: resovle configSchema from either configSchema or config.schema + const newConfigSchema = options.config?.schema; + const configSchema = newConfigSchema + ? createSchemaFromZod(innerZ => + innerZ.object( + Object.fromEntries( + Object.entries(newConfigSchema).map(([k, v]) => [k, v(innerZ)]), + ), + ), + ) + : options.configSchema; + return { $$type: '@backstage/ExtensionDefinition', version: 'v1', @@ -172,8 +178,7 @@ export function createExtension< disabled: options.disabled ?? false, inputs: options.inputs ?? {}, output: options.output, - configSchema: options.configSchema, - config: options.config, + configSchema, factory({ inputs, ...rest }) { // TODO: Simplify this, but TS wouldn't infer the input type for some reason return options.factory({ diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 9cd5689a95..c54131622f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -33,7 +33,7 @@ export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { kind: string; @@ -51,9 +51,9 @@ export interface CreateExtensionBlueprintOptions< params: TParams, context: { node: AppNode; - config: Expand<{ + config: { [key in keyof TConfigSchema]: z.infer; - }>; + }; inputs: Expand>; }, ): Expand>; @@ -68,7 +68,7 @@ export interface ExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfig extends { [key in string]: unknown }, TDataRefs extends AnyExtensionDataMap, > { dataRefs: TDataRefs; @@ -79,7 +79,7 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make( + make( args: { namespace?: string; name?: string; @@ -100,23 +100,17 @@ export interface ExtensionBlueprint< originalFactory: ( params: TParams, context?: { - config?: Expand<{ - [key in keyof TConfigSchema]: z.infer; - }>; + config?: TConfig; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: Expand< - { - [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] - >; - } & { - [key in keyof TConfigSchema]: z.infer; - } - >; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & TConfig; inputs: Expand>; }, ): Expand>; @@ -130,8 +124,7 @@ export interface ExtensionBlueprint< [key in keyof TExtensionConfigSchema]: z.infer< TExtensionConfigSchema[key] >; - }, - TExtensionConfigSchema + } & TConfig >; } @@ -142,7 +135,7 @@ class ExtensionBlueprintImpl< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap, > { constructor( @@ -160,7 +153,7 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; public make< - TExtensionConfigSchema extends { [key: string]: z.ZodTypeAny }, + TExtensionConfigSchema extends { [key in string]: z.ZodTypeAny }, >(args: { namespace?: string; name?: string; @@ -171,45 +164,53 @@ class ExtensionBlueprintImpl< params?: TParams; config?: { schema: { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + [key in keyof TExtensionConfigSchema]: ( + zImpl: typeof z, + ) => TExtensionConfigSchema[key]; }; }; factory?( originalFactory: ( params: TParams, context?: { - config?: Expand<{ + config?: { [key in keyof TConfigSchema]: z.infer; - }>; + }; inputs?: Expand>; }, ) => Expand>, context: { node: AppNode; - config: Expand< - { - [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] - >; - } & { - [key in keyof TConfigSchema]: z.infer; - } - >; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer; + }; inputs: Expand>; }, ): Expand>; }): ExtensionDefinition< - Expand< - { - [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] - >; - } & { - [key in keyof TConfigSchema]: z.infer; - } - >, - Expand + { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & { + [key in keyof TConfigSchema]: z.infer; + } > { + const schema = { + ...this.options.config?.schema, + ...args.config, + } as { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + } & { + [key in keyof TExtensionConfigSchema]: ( + zImpl: typeof z, + ) => TExtensionConfigSchema[key]; + }; + return createExtension({ kind: this.options.kind, namespace: args.namespace ?? this.options.namespace, @@ -218,31 +219,16 @@ class ExtensionBlueprintImpl< disabled: args.disabled ?? this.options.disabled, inputs: args.inputs ?? this.options.inputs, output: args.output ?? this.options.output, - config: { - schema: { - ...this.options.config?.schema, - ...args.config, - } as Expand< - { - [key in keyof TConfigSchema]: ( - zImpl: typeof z, - ) => TConfigSchema[key]; - } & { - [key in keyof TExtensionConfigSchema]: ( - zImpl: typeof z, - ) => TExtensionConfigSchema[key]; - } - >, - }, + config: Object.keys(schema).length === 0 ? undefined : { schema }, factory: ({ node, config, inputs }) => { if (args.factory) { return args.factory( ( innerParams: TParams, innerContext?: { - config?: Expand<{ + config?: { [key in keyof TConfigSchema]: z.infer; - }>; + }; inputs?: Expand>; }, ) => @@ -280,7 +266,7 @@ export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodTypeAny }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< @@ -290,6 +276,12 @@ export function createExtensionBlueprint< TConfigSchema, TDataRefs >, -): ExtensionBlueprint { - return new ExtensionBlueprintImpl(options) as any; +): ExtensionBlueprint< + TParams, + TInputs, + TOutput, + { [key in keyof TConfigSchema]: z.infer }, + TDataRefs +> { + return new ExtensionBlueprintImpl(options); } diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index afd03965ae..59a387adf0 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -6,72 +6,17 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { objectUtil } from 'zod'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { ZodArray } from 'zod'; -import { ZodBoolean } from 'zod'; -import { ZodEnum } from 'zod'; -import { ZodNativeEnum } from 'zod'; -import { ZodNumber } from 'zod'; -import { ZodOptional } from 'zod'; -import { ZodString } from 'zod'; -import { ZodTuple } from 'zod'; // Warning: (ae-missing-release-tag) "CatalogGraphEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const CatalogGraphEntityCard: ExtensionDefinition< { - filter?: string | undefined; - height?: number | undefined; - curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; - direction?: Direction | undefined; - title?: string | undefined; - zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; - relations?: string[] | undefined; - maxDepth?: number | undefined; - kinds?: string[] | undefined; - unidirectional?: boolean | undefined; - mergeRelations?: boolean | undefined; - relationPairs?: [string, string][] | undefined; - }, - objectUtil.extendShape< - objectUtil.extendShape< - { - filter: ZodOptional; - }, - { - title: ZodOptional; - height: ZodOptional; - } - >, - { - kinds: ZodOptional>; - relations: ZodOptional>; - maxDepth: ZodOptional; - unidirectional: ZodOptional; - mergeRelations: ZodOptional; - direction: ZodOptional>; - relationPairs: ZodOptional< - ZodArray, 'many'> - >; - zoom: ZodOptional>; - curve: ZodOptional>; - } - >, - { - filter?: string | undefined; - height?: number | undefined; - curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; - direction?: Direction | undefined; - title?: string | undefined; - zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; - relations?: string[] | undefined; - maxDepth?: number | undefined; - kinds?: string[] | undefined; - unidirectional?: boolean | undefined; - mergeRelations?: boolean | undefined; - relationPairs?: [string, string][] | undefined; + title: string | undefined; + height: number | undefined; + } & { + filter: string | undefined; } >; @@ -93,9 +38,5 @@ const _default: BackstagePlugin< >; export default _default; -// Warnings were encountered during analysis: -// -// src/alpha.d.ts:6:5 - (ae-forgotten-export) The symbol "Direction" needs to be exported by the entry point alpha.d.ts - // (No @packageDocumentation comment for this package) ``` From cac9c3f851221bebef7ae9a3a0b6cc3b816eb9a3 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 11:48:07 +0200 Subject: [PATCH 4/8] chore: added some tests Signed-off-by: blam --- .../src/schema/createSchemaFromZod.ts | 5 +- .../src/wiring/createExtension.test.ts | 62 ++++++++++++++++++ .../src/wiring/createExtension.ts | 17 +++-- .../wiring/createExtensionBlueprint.test.tsx | 65 +++++++++++++++++++ .../src/wiring/createExtensionBlueprint.ts | 12 ++-- 5 files changed, 149 insertions(+), 12 deletions(-) diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index bfaf57f522..c69ef5dc60 100644 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -19,7 +19,10 @@ import { z, ZodSchema, ZodTypeDef } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { PortableSchema } from './types'; -/** @public */ +/** + * @public + * @deprecated Use the `config.schema` option of `createExtension` instead, or use `createExtensionBlueprint`. + */ export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, ): PortableSchema { diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 4323737540..468da21eb7 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -291,4 +291,66 @@ describe('createExtension', () => { 'ExtensionDefinition{namespace=test,attachTo=root@default}', ); }); + + it('should create an extension with config', () => { + const extension = createExtension({ + namespace: 'test', + attachTo: { id: 'root', input: 'default' }, + config: { + schema: { + foo: z => z.string(), + bar: z => z.string().default('bar'), + baz: z => z.string().optional(), + }, + }, + output: { + foo: stringData, + }, + factory({ config }) { + const a1: string = config.foo; + const a2: string = config.bar; + // @ts-expect-error + const a3: string = config.baz; + // @ts-expect-error + const c1: number = config.foo; + // @ts-expect-error + const c2: number = config.bar; + // @ts-expect-error + const c3: number = config.baz; + unused(a1, a2, a3, c1, c2, c3); + + return { + foo: 'bar', + }; + }, + }); + expect(extension.namespace).toBe('test'); + expect(String(extension)).toBe( + 'ExtensionDefinition{namespace=test,attachTo=root@default}', + ); + + expect( + extension.configSchema?.parse({ + foo: 'x', + bar: 'y', + baz: 'z', + qux: 'w', + }), + ).toEqual({ + foo: 'x', + bar: 'y', + baz: 'z', + }); + expect( + extension.configSchema?.parse({ + foo: 'x', + }), + ).toEqual({ + foo: 'x', + bar: 'bar', + }); + expect(() => extension.configSchema?.parse({})).toThrow( + "Missing required value at 'foo'", + ); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 6fa3a50634..7783f8e14d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -82,7 +82,7 @@ export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodType }, > { kind?: string; namespace?: string; @@ -100,7 +100,9 @@ export interface CreateExtensionOptions< }; factory(context: { node: AppNode; - config: TConfig; + config: TConfig & { + [key in keyof TConfigSchema]: z.infer; + }; inputs: Expand>; }): Expand>; } @@ -152,12 +154,14 @@ export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: z.ZodTypeAny }, + TConfigSchema extends { [key: string]: z.ZodType }, >( options: CreateExtensionOptions, ): ExtensionDefinition { - // TODO: resovle configSchema from either configSchema or config.schema const newConfigSchema = options.config?.schema; + if (newConfigSchema && options.configSchema) { + throw new Error(`Cannot provide both configSchema and config.schema`); + } const configSchema = newConfigSchema ? createSchemaFromZod(innerZ => innerZ.object( @@ -179,10 +183,13 @@ export function createExtension< inputs: options.inputs ?? {}, output: options.output, configSchema, - factory({ inputs, ...rest }) { + factory({ inputs, config, ...rest }) { // TODO: Simplify this, but TS wouldn't infer the input type for some reason return options.factory({ inputs: inputs as Expand>, + config: config as TConfig & { + [key in keyof TConfigSchema]: z.infer; + }, ...rest, }); }, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index d2373518a3..cf7de0fed3 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -20,6 +20,8 @@ import { createExtensionBlueprint } from './createExtensionBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; import { createExtensionDataRef } from './createExtensionDataRef'; +function unused(..._any: any[]) {} + describe('createExtensionBlueprint', () => { it('should allow creation of extension blueprints', () => { const TestExtensionBlueprint = createExtensionBlueprint({ @@ -124,4 +126,67 @@ describe('createExtensionBlueprint', () => { data: dataRef, }); }); + + it('should allow defining a config schema with additional properties in the instance', () => { + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + config: { + schema: { + text: z => z.string(), + }, + }, + factory(_, { config }) { + // @ts-expect-error + const b = config.something; + + const a: string = config.text; + unused(a); + + expect(config.text).toBe('Hello, world!'); + + return { + element:

{config.text}

, + }; + }, + }); + + const extension = TestExtensionBlueprint.make({ + name: 'my-extension', + params: { + text: 'Hello, world!', + }, + config: { + schema: { + something: z => z.string(), + defaulted: z => z.string().optional().default('default'), + }, + }, + factory(origFactory, { config }) { + const b: string = config.something; + const c: string = config.text; + const d: string = config.defaulted; + + unused(b, c, d); + + expect(config.text).toBe('Hello, world!'); + expect(config.something).toBe('something new!'); + expect(config.defaulted).toBe('lolz'); + return origFactory({}); + }, + }); + + expect.assertions(4); + + createExtensionTester(extension, { + config: { + something: 'something new!', + text: 'Hello, world!', + defaulted: 'lolz', + }, + }).render(); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index c54131622f..56edcd8141 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -33,7 +33,7 @@ export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodType }, TDataRefs extends AnyExtensionDataMap, > { kind: string; @@ -79,7 +79,7 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make( + make( args: { namespace?: string; name?: string; @@ -135,7 +135,7 @@ class ExtensionBlueprintImpl< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodType }, TDataRefs extends AnyExtensionDataMap, > { constructor( @@ -153,7 +153,7 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; public make< - TExtensionConfigSchema extends { [key in string]: z.ZodTypeAny }, + TExtensionConfigSchema extends { [key in string]: z.ZodType }, >(args: { namespace?: string; name?: string; @@ -202,7 +202,7 @@ class ExtensionBlueprintImpl< > { const schema = { ...this.options.config?.schema, - ...args.config, + ...args.config?.schema, } as { [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; } & { @@ -266,7 +266,7 @@ export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodTypeAny }, + TConfigSchema extends { [key in string]: z.ZodType }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< From 97157c547052fdd46e86c1a28f6c99387a3f89d7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 11:51:52 +0200 Subject: [PATCH 5/8] chore: do not allow redefing types from blueprint in make config schema Signed-off-by: blam --- .../wiring/createExtensionBlueprint.test.tsx | 36 +++++++++++++++++++ .../src/wiring/createExtensionBlueprint.ts | 2 ++ 2 files changed, 38 insertions(+) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index cf7de0fed3..209e9759bd 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -189,4 +189,40 @@ describe('createExtensionBlueprint', () => { }, }).render(); }); + + it('should not allow overlapping config keys', () => { + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + config: { + schema: { + text: z => z.string(), + }, + }, + factory(params: { text: string }) { + return { + element:
{params.text}
, + }; + }, + }); + + TestExtensionBlueprint.make({ + name: 'my-extension', + params: { + text: 'Hello, world!', + }, + config: { + schema: { + // @ts-expect-error + text: z => z.number(), + something: z => z.string(), + }, + }, + }); + + expect('test').toBe('test'); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 56edcd8141..21c10f3c0e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -92,6 +92,8 @@ export interface ExtensionBlueprint< [key in keyof TExtensionConfigSchema]: ( zImpl: typeof z, ) => TExtensionConfigSchema[key]; + } & { + [key in keyof TConfig]?: never; }; }; } & ( From e59ff0b2bc1b637df7b476dc323aa44d7e92ca37 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 12:06:53 +0200 Subject: [PATCH 6/8] chore: tidy up Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 121 ++++++++++++------ .../src/extensions/IconBundleBlueprint.ts | 36 ------ .../src/app/createExtensionTester.tsx | 4 +- plugins/catalog-graph/api-report-alpha.md | 13 -- plugins/catalog-graph/src/alpha.tsx | 79 +++--------- plugins/catalog-react/api-report-alpha.md | 56 ++------ plugins/catalog-react/src/alpha.tsx | 51 -------- plugins/catalog/api-report-alpha.md | 35 +---- plugins/catalog/src/alpha/index.ts | 2 - plugins/search-react/api-report-alpha.md | 3 +- plugins/search/api-report-alpha.md | 26 ++-- plugins/techdocs/api-report-alpha.md | 18 +-- plugins/user-settings/api-report-alpha.md | 10 +- 13 files changed, 130 insertions(+), 324 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 0c7c3b1d92..33ac02437c 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -89,7 +89,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { vmwareCloudAuthApiRef } from '@backstage/core-plugin-api'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; -import { ZodObject } from 'zod'; import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; @@ -392,7 +391,7 @@ export function createApiExtension< configSchema?: PortableSchema; inputs?: TInputs; }, -): ExtensionDefinition>; +): ExtensionDefinition; // @public (undocumented) export namespace createApiExtension { @@ -488,7 +487,7 @@ export function createComponentExtension< inputs: Expand>; }) => ComponentType; }; -}): ExtensionDefinition>; +}): ExtensionDefinition; // @public (undocumented) export namespace createComponentExtension { @@ -512,35 +511,49 @@ export function createComponentRef(options: { export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, - TConfigSchema extends z.ZodObject, - TConfig extends z.infer, + TConfig, + TConfigSchema extends { + [key: string]: z.ZodType; + }, >( - options: CreateExtensionOptions, -): ExtensionDefinition; + options: CreateExtensionOptions, +): ExtensionDefinition; // @public export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { + [key in string]: z.ZodType; + }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< TParams, TInputs, TOutput, - TConfig, + TConfigSchema, TDataRefs >, -): ExtensionBlueprint; +): ExtensionBlueprint< + TParams, + TInputs, + TOutput, + { + [key in keyof TConfigSchema]: z.infer; + }, + TDataRefs +>; // @public (undocumented) export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfigSchema extends { + [key in string]: z.ZodType; + }, TDataRefs extends AnyExtensionDataMap, > { // (undocumented) @@ -549,7 +562,11 @@ export interface CreateExtensionBlueprintOptions< input: string; }; // (undocumented) - configSchema?: PortableSchema; + config?: { + schema: { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + }; + }; // (undocumented) dataRefs?: TDataRefs; // (undocumented) @@ -559,7 +576,9 @@ export interface CreateExtensionBlueprintOptions< params: TParams, context: { node: AppNode; - config: TConfig; + config: { + [key in keyof TConfigSchema]: z.infer; + }; inputs: Expand>; }, ): Expand>; @@ -607,8 +626,10 @@ export function createExtensionInput< export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, - TConfigSchema extends z.ZodObject, - TConfig extends z.infer, + TConfig, + TConfigSchema extends { + [key: string]: z.ZodType; + }, > { // (undocumented) attachTo: { @@ -617,7 +638,9 @@ export interface CreateExtensionOptions< }; // (undocumented) config?: { - schema: (context: { z: typeof z }) => TConfigSchema; + schema: { + [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; + }; }; // @deprecated (undocumented) configSchema?: PortableSchema; @@ -626,7 +649,9 @@ export interface CreateExtensionOptions< // (undocumented) factory(context: { node: AppNode; - config: TConfig; + config: TConfig & { + [key in keyof TConfigSchema]: z.infer; + }; inputs: Expand>; }): Expand>; // (undocumented) @@ -679,12 +704,9 @@ export function createNavItemExtension(options: { routeRef: RouteRef; title: string; icon: IconComponent_2; -}): ExtensionDefinition< - { - title: string; - }, - ZodObject ->; +}): ExtensionDefinition<{ + title: string; +}>; // @public (undocumented) export namespace createNavItemExtension { @@ -706,7 +728,7 @@ export function createNavLogoExtension(options: { namespace?: string; logoIcon: JSX.Element; logoFull: JSX.Element; -}): ExtensionDefinition>; +}): ExtensionDefinition; // @public (undocumented) export namespace createNavLogoExtension { @@ -814,7 +836,7 @@ export namespace createRouterExtension { >; } -// @public (undocumented) +// @public @deprecated (undocumented) export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, ): PortableSchema; @@ -861,7 +883,7 @@ export function createSubRouteRef< // @public (undocumented) export function createThemeExtension( theme: AppTheme, -): ExtensionDefinition>; +): ExtensionDefinition; // @public (undocumented) export namespace createThemeExtension { @@ -877,7 +899,7 @@ export namespace createThemeExtension { export function createTranslationExtension(options: { name?: string; resource: TranslationResource | TranslationMessages; -}): ExtensionDefinition>; +}): ExtensionDefinition; // @public (undocumented) export namespace createTranslationExtension { @@ -936,12 +958,18 @@ export interface ExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfig, + TConfig extends { + [key in string]: unknown; + }, TDataRefs extends AnyExtensionDataMap, > { // (undocumented) dataRefs: TDataRefs; - make( + make< + TExtensionConfigSchema extends { + [key in string]: z.ZodType; + }, + >( args: { namespace?: string; name?: string; @@ -952,7 +980,15 @@ export interface ExtensionBlueprint< disabled?: boolean; inputs?: TInputs; output?: TOutput; - configSchema?: PortableSchema; + config?: { + schema: { + [key in keyof TExtensionConfigSchema]: ( + zImpl: typeof z, + ) => TExtensionConfigSchema[key]; + } & { + [key in keyof TConfig]?: never; + }; + }; } & ( | { factory( @@ -965,7 +1001,11 @@ export interface ExtensionBlueprint< ) => Expand>, context: { node: AppNode; - config: TConfig; + config: { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & TConfig; inputs: Expand>; }, ): Expand>; @@ -974,7 +1014,13 @@ export interface ExtensionBlueprint< params: TParams; } ), - ): ExtensionDefinition; + ): ExtensionDefinition< + { + [key in keyof TExtensionConfigSchema]: z.infer< + TExtensionConfigSchema[key] + >; + } & TConfig + >; } // @public (undocumented) @@ -1021,10 +1067,7 @@ export type ExtensionDataValues = { }; // @public (undocumented) -export interface ExtensionDefinition< - TConfig, - TConfigSchema extends z.ZodObject = any, -> { +export interface ExtensionDefinition { // (undocumented) $$type: '@backstage/ExtensionDefinition'; // (undocumented) @@ -1035,8 +1078,6 @@ export interface ExtensionDefinition< // (undocumented) readonly configSchema?: PortableSchema; // (undocumented) - readonly configSchema2?: TConfigSchema; - // (undocumented) readonly disabled: boolean; // (undocumented) readonly kind?: string; @@ -1134,7 +1175,9 @@ export const IconBundleBlueprint: ExtensionBlueprint< {} >; }, - unknown, + { + [x: string]: any; + }, { icons: ConfigurableExtensionDataRef< 'core.icons', diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts index aba3303f2f..2ad1404102 100644 --- a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts @@ -33,39 +33,3 @@ export const IconBundleBlueprint = createExtensionBlueprint({ icons: iconsDataRef, }, }); - -IconBundleBlueprint.make({ - name: 'asd', - params: { - icons: { - 'icon-name': () => null, - }, - }, -}); - -IconBundleBlueprint.make({ - name: 'asd', - params: { - icons: { - 'icon-name': () => null, - }, - }, -}); - -IconBundleBlueprint.make({ - name: 'asd', - factory() { - return { - icons: { - 'icon-name': () => null, - }, - }; - }, -}); - -IconBundleBlueprint.make({ - name: 'asd', - factory(f) { - return f({ icons: { 'icon-name': () => null } }); - }, -}); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index d6be346002..a98879fbba 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -102,11 +102,11 @@ export class ExtensionTester { ...output, path: coreExtensionData.routePath, }, - factory: (params: any) => ({ + factory: params => ({ ...factory(params), path: '/', }), - } as any); + }); tester.add(extension, options); return tester; } diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 59a387adf0..3895f12fcf 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -4,22 +4,9 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; -// Warning: (ae-missing-release-tag) "CatalogGraphEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const CatalogGraphEntityCard: ExtensionDefinition< - { - title: string | undefined; - height: number | undefined; - } & { - filter: string | undefined; - } ->; - // @public (undocumented) const _default: BackstagePlugin< { diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index cf663406fa..6d8429332e 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -24,7 +24,7 @@ import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; import { Direction } from './components'; @@ -46,67 +46,24 @@ function getEntityGraphRelationsConfigSchema( }); } -export const CatalogGraphEntityCard = EntityCardBlueprint.make({ +const CatalogGraphEntityCard = createEntityCardExtension({ name: 'relations', - // configSchema: origSchema => - // createSchemaFromZod(z => - // z - // .object({ - // // Filter is a config required to all entity cards - // // filter: z.string().optional(), - // title: z.string().optional(), - // height: z.number().optional(), - // // Skipping a "variant" config for now, defaulting to "gridItem" in the component - // // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - // }) - // .merge(getEntityGraphRelationsConfigSchema(z)) - // .merge(origSchema), - // ), - - // config: { - // schema: ({ z, parent }) => - // parent - // .merge({ - // title: z.string().optional(), - // height: z.number().optional(), - // }) - // .merge(getEntityGraphRelationsConfigSchema(z)), - // }, - - // config: { - // schema: ({ z, parent }) => - // parent - // .extend({ - // title: z.string().optional(), - // height: z.number().optional(), - // }) - // .merge(getEntityGraphRelationsConfigSchema(z)), - // // .merge(parent) - // }, - - // TODO: implement this and check the types. 👋 zod types. - config: { - schema: { - title: z => z.string().optional(), - height: z => z.number().optional(), - }, - }, - - // config: { - // schema: z => ({ - // title: z.string().optional(), - // height: z.number().optional(), - // }), - // }, - - factory(origFactory, { config: { filter: _, ...props } }) { - return origFactory({ - loader: async () => - import('./components/CatalogGraphCard').then(m => - compatWrapper(), - ), - }); - }, + configSchema: createSchemaFromZod(z => + z + .object({ + // Filter is a config required to all entity cards + filter: z.string().optional(), + title: z.string().optional(), + height: z.number().optional(), + // Skipping a "variant" config for now, defaulting to "gridItem" in the component + // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + }) + .merge(getEntityGraphRelationsConfigSchema(z)), + ), + loader: async ({ config: { filter, ...props } }) => + import('./components/CatalogGraphCard').then(m => + compatWrapper(), + ), }); const CatalogGraphPage = createPageExtension({ diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index ccf8205f59..d2bfb29458 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -3,20 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// + import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { default as React_2 } from 'react'; import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { ZodOptional } from 'zod'; -import { ZodRawShape } from 'zod'; -import { ZodString } from 'zod'; // @alpha (undocumented) export const catalogExtensionData: { @@ -118,7 +115,7 @@ export function createEntityCardExtension< config: TConfig; inputs: Expand>; }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) export function createEntityContentExtension< @@ -141,48 +138,11 @@ export function createEntityContentExtension< loader: (options: { inputs: Expand>; }) => Promise; -}): ExtensionDefinition< - { - title: string; - path: string; - filter?: string | undefined; - }, - ZodRawShape ->; - -// @alpha (undocumented) -export const EntityCardBlueprint: ExtensionBlueprint< - { - filter?: string | ((entity: Entity) => boolean) | undefined; - loader: () => Promise; - }, - AnyExtensionInputMap, - { - element: ConfigurableExtensionDataRef< - 'core.reactElement', - React_2.JSX.Element, - {} - >; - filterFunction: ConfigurableExtensionDataRef< - 'catalog.entity-filter-function', - (entity: Entity) => boolean, - ((entity: Entity) => boolean) & { - optional: true; - } - >; - filterExpression: ConfigurableExtensionDataRef< - 'catalog.entity-filter-expression', - string, - string & { - optional: true; - } - >; - }, - { - filter: ZodOptional; - }, - never ->; +}): ExtensionDefinition<{ + title: string; + path: string; + filter?: string | undefined; +}>; // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx index f397c76ce0..e81b8e5ebf 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha.tsx @@ -22,7 +22,6 @@ import { RouteRef, coreExtensionData, createExtension, - createExtensionBlueprint, createExtensionDataRef, createSchemaFromZod, } from '@backstage/frontend-plugin-api'; @@ -48,49 +47,6 @@ export const catalogExtensionData = { }), }; -/** @alpha */ -export const EntityCardBlueprint = createExtensionBlueprint({ - kind: 'entity-card', - attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, - // configSchema: createSchemaFromZod(z => - // z.object({ - // filter: z.string().optional(), - // }), - // ), - config: { - schema: { - filter: z => z.string().optional(), - }, - }, - output: { - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - factory( - params: { - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: () => Promise; - }, - { node, config }, - ) { - const ExtensionComponent = lazy(() => - params.loader().then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - ...mergeFilters({ config, options: params }), - }; - }, -}); - // TODO: Figure out how to merge with provided config schema /** @alpha */ export function createEntityCardExtension< @@ -198,13 +154,6 @@ export function createEntityContentExtension< filter: z.string().optional(), }), ), - // config: { - // schema: ({ z }) => ({ - // path: z.string().default(options.defaultPath), - // title: z.string().default(options.defaultTitle), - // filter: z.string().optional(), - // }), - // }, factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 03711b7d3d..102e2a7aa3 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -12,39 +12,6 @@ import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { ZodObject } from 'zod'; - -// Warning: (ae-missing-release-tag) "catalogAboutEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const catalogAboutEntityCard: ExtensionDefinition< - { - filter?: string | undefined; - }, - ZodObject ->; - -// Warning: (ae-missing-release-tag) "catalogLinksEntityCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const catalogLinksEntityCard: ExtensionDefinition< - { - filter?: string | undefined; - }, - ZodObject ->; - -// Warning: (ae-missing-release-tag) "catalogOverviewEntityContent" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const catalogOverviewEntityContent: ExtensionDefinition< - { - title: string; - path: string; - filter?: string | undefined; - }, - ZodObject ->; // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< @@ -139,7 +106,7 @@ export function createCatalogFilterExtension< inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition>; +}): ExtensionDefinition; // @alpha (undocumented) const _default: BackstagePlugin< diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index acf61865ba..06a78ec6ea 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -16,5 +16,3 @@ export { default } from './plugin'; export { createCatalogFilterExtension } from './createCatalogFilterExtension'; -export { catalogAboutEntityCard, catalogLinksEntityCard } from './entityCards'; -export { catalogOverviewEntityContent } from './entityContents'; diff --git a/plugins/search-react/api-report-alpha.md b/plugins/search-react/api-report-alpha.md index 6160759042..aea85d0f81 100644 --- a/plugins/search-react/api-report-alpha.md +++ b/plugins/search-react/api-report-alpha.md @@ -11,7 +11,6 @@ import { ListItemProps } from '@material-ui/core/ListItem'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; -import { ZodObject } from 'zod'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { @@ -26,7 +25,7 @@ export function createSearchResultListItemExtension< }, >( options: SearchResultItemExtensionOptions, -): ExtensionDefinition>; +): ExtensionDefinition; // @alpha (undocumented) export namespace createSearchResultListItemExtension { diff --git a/plugins/search/api-report-alpha.md b/plugins/search/api-report-alpha.md index ade5f530ef..db2999ba25 100644 --- a/plugins/search/api-report-alpha.md +++ b/plugins/search/api-report-alpha.md @@ -6,7 +6,6 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -18,27 +17,18 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const searchApi: ExtensionDefinition< - {}, - ZodObject ->; +export const searchApi: ExtensionDefinition<{}>; // @alpha (undocumented) -export const searchNavItem: ExtensionDefinition< - { - title: string; - }, - ZodObject ->; +export const searchNavItem: ExtensionDefinition<{ + title: string; +}>; // @alpha (undocumented) -export const searchPage: ExtensionDefinition< - { - path: string; - noTrack: boolean; - }, - any ->; +export const searchPage: ExtensionDefinition<{ + path: string; + noTrack: boolean; +}>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/api-report-alpha.md b/plugins/techdocs/api-report-alpha.md index fc57370a53..cf9f5fa9a3 100644 --- a/plugins/techdocs/api-report-alpha.md +++ b/plugins/techdocs/api-report-alpha.md @@ -6,7 +6,6 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -24,16 +23,13 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const techDocsSearchResultListItemExtension: ExtensionDefinition< - { - lineClamp: number; - noTrack: boolean; - asListItem: boolean; - asLink: boolean; - title?: string | undefined; - }, - ZodObject ->; +export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ + lineClamp: number; + noTrack: boolean; + asListItem: boolean; + asLink: boolean; + title?: string | undefined; +}>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/user-settings/api-report-alpha.md b/plugins/user-settings/api-report-alpha.md index db4373fd43..b06fd1e583 100644 --- a/plugins/user-settings/api-report-alpha.md +++ b/plugins/user-settings/api-report-alpha.md @@ -7,7 +7,6 @@ import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; -import { ZodObject } from 'zod'; // @alpha (undocumented) const _default: BackstagePlugin< @@ -19,12 +18,9 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const settingsNavItem: ExtensionDefinition< - { - title: string; - }, - ZodObject ->; +export const settingsNavItem: ExtensionDefinition<{ + title: string; +}>; // @alpha (undocumented) export const userSettingsTranslationRef: TranslationRef< From 3fb421d1b05281ffdbdfbb42ceb5554931a0aa2f Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 12:48:49 +0200 Subject: [PATCH 7/8] chore: added changeset Signed-off-by: blam --- .changeset/sweet-oranges-buy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sweet-oranges-buy.md diff --git a/.changeset/sweet-oranges-buy.md b/.changeset/sweet-oranges-buy.md new file mode 100644 index 0000000000..d9f3343e3d --- /dev/null +++ b/.changeset/sweet-oranges-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. From e9673327852493d196430c0c4eddf12e2effe8bb Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Jul 2024 13:40:43 +0200 Subject: [PATCH 8/8] chore: small refactor and fixing some of the types Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 51 +++++------ .../src/wiring/createExtension.ts | 12 ++- .../wiring/createExtensionBlueprint.test.tsx | 49 +++++++++++ .../src/wiring/createExtensionBlueprint.ts | 86 ++++++++++--------- 4 files changed, 122 insertions(+), 76 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 33ac02437c..45ed51b415 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -513,7 +513,7 @@ export function createExtension< TInputs extends AnyExtensionInputMap, TConfig, TConfigSchema extends { - [key: string]: z.ZodType; + [key: string]: (zImpl: typeof z) => z.ZodType; }, >( options: CreateExtensionOptions, @@ -525,7 +525,7 @@ export function createExtensionBlueprint< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfigSchema extends { - [key in string]: z.ZodType; + [key in string]: (zImpl: typeof z) => z.ZodType; }, TDataRefs extends AnyExtensionDataMap = never, >( @@ -540,9 +540,11 @@ export function createExtensionBlueprint< TParams, TInputs, TOutput, - { - [key in keyof TConfigSchema]: z.infer; - }, + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, TDataRefs >; @@ -552,7 +554,7 @@ export interface CreateExtensionBlueprintOptions< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfigSchema extends { - [key in string]: z.ZodType; + [key in string]: (zImpl: typeof z) => z.ZodType; }, TDataRefs extends AnyExtensionDataMap, > { @@ -563,9 +565,7 @@ export interface CreateExtensionBlueprintOptions< }; // (undocumented) config?: { - schema: { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; - }; + schema: TConfigSchema; }; // (undocumented) dataRefs?: TDataRefs; @@ -577,7 +577,7 @@ export interface CreateExtensionBlueprintOptions< context: { node: AppNode; config: { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }; inputs: Expand>; }, @@ -628,7 +628,7 @@ export interface CreateExtensionOptions< TInputs extends AnyExtensionInputMap, TConfig, TConfigSchema extends { - [key: string]: z.ZodType; + [key: string]: (zImpl: typeof z) => z.ZodType; }, > { // (undocumented) @@ -638,9 +638,7 @@ export interface CreateExtensionOptions< }; // (undocumented) config?: { - schema: { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; - }; + schema: TConfigSchema; }; // @deprecated (undocumented) configSchema?: PortableSchema; @@ -650,7 +648,7 @@ export interface CreateExtensionOptions< factory(context: { node: AppNode; config: TConfig & { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }; inputs: Expand>; }): Expand>; @@ -967,7 +965,7 @@ export interface ExtensionBlueprint< dataRefs: TDataRefs; make< TExtensionConfigSchema extends { - [key in string]: z.ZodType; + [key in string]: (zImpl: typeof z) => z.ZodType; }, >( args: { @@ -981,12 +979,9 @@ export interface ExtensionBlueprint< inputs?: TInputs; output?: TOutput; config?: { - schema: { - [key in keyof TExtensionConfigSchema]: ( - zImpl: typeof z, - ) => TExtensionConfigSchema[key]; - } & { - [key in keyof TConfig]?: never; + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; }; }; } & ( @@ -1001,11 +996,11 @@ export interface ExtensionBlueprint< ) => Expand>, context: { node: AppNode; - config: { + config: TConfig & { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; - } & TConfig; + }; inputs: Expand>; }, ): Expand>; @@ -1017,7 +1012,7 @@ export interface ExtensionBlueprint< ): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; } & TConfig >; @@ -1175,9 +1170,7 @@ export const IconBundleBlueprint: ExtensionBlueprint< {} >; }, - { - [x: string]: any; - }, + {}, { icons: ConfigurableExtensionDataRef< 'core.icons', diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 7783f8e14d..ac91557906 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -82,7 +82,7 @@ export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: z.ZodType }, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, > { kind?: string; namespace?: string; @@ -94,14 +94,12 @@ export interface CreateExtensionOptions< /** @deprecated - use `config.schema` instead */ configSchema?: PortableSchema; config?: { - schema: { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; - }; + schema: TConfigSchema; }; factory(context: { node: AppNode; config: TConfig & { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }; inputs: Expand>; }): Expand>; @@ -154,7 +152,7 @@ export function createExtension< TOutput extends AnyExtensionDataMap, TInputs extends AnyExtensionInputMap, TConfig, - TConfigSchema extends { [key: string]: z.ZodType }, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, >( options: CreateExtensionOptions, ): ExtensionDefinition { @@ -188,7 +186,7 @@ export function createExtension< return options.factory({ inputs: inputs as Expand>, config: config as TConfig & { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }, ...rest, }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 209e9759bd..cb8855d892 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -225,4 +225,53 @@ describe('createExtensionBlueprint', () => { expect('test').toBe('test'); }); + + it('should allow setting config when one was not already defined in the blueprint', () => { + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + factory(_, { config }) { + // @ts-expect-error + const b = config.something; + + return { + element:
, + }; + }, + }); + + const extension = TestExtensionBlueprint.make({ + name: 'my-extension', + params: { + text: 'Hello, world!', + }, + config: { + schema: { + something: z => z.string(), + defaulted: z => z.string().optional().default('default'), + }, + }, + factory(origFactory, { config }) { + const b: string = config.something; + + unused(b); + + expect(config.something).toBe('something new!'); + expect(config.defaulted).toBe('lolz'); + return origFactory({}); + }, + }); + + expect.assertions(2); + + createExtensionTester(extension, { + config: { + something: 'something new!', + defaulted: 'lolz', + }, + }).render(); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 21c10f3c0e..ce3e650342 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -33,7 +33,7 @@ export interface CreateExtensionBlueprintOptions< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodType }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, TDataRefs extends AnyExtensionDataMap, > { kind: string; @@ -43,16 +43,14 @@ export interface CreateExtensionBlueprintOptions< inputs?: TInputs; output: TOutput; config?: { - schema: { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; - }; + schema: TConfigSchema; }; factory( params: TParams, context: { node: AppNode; config: { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }; inputs: Expand>; }, @@ -79,7 +77,11 @@ export interface ExtensionBlueprint< * You must either pass `params` directly, or define a `factory` that can * optionally call the original factory with the same params. */ - make( + make< + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + >( args: { namespace?: string; name?: string; @@ -88,12 +90,9 @@ export interface ExtensionBlueprint< inputs?: TInputs; output?: TOutput; config?: { - schema: { - [key in keyof TExtensionConfigSchema]: ( - zImpl: typeof z, - ) => TExtensionConfigSchema[key]; - } & { - [key in keyof TConfig]?: never; + schema: TExtensionConfigSchema & { + [KName in keyof TConfig]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; }; }; } & ( @@ -108,11 +107,11 @@ export interface ExtensionBlueprint< ) => Expand>, context: { node: AppNode; - config: { + config: TConfig & { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; - } & TConfig; + }; inputs: Expand>; }, ): Expand>; @@ -124,7 +123,7 @@ export interface ExtensionBlueprint< ): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; } & TConfig >; @@ -137,7 +136,7 @@ class ExtensionBlueprintImpl< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodType }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, TDataRefs extends AnyExtensionDataMap, > { constructor( @@ -155,7 +154,9 @@ class ExtensionBlueprintImpl< dataRefs: TDataRefs; public make< - TExtensionConfigSchema extends { [key in string]: z.ZodType }, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, >(args: { namespace?: string; name?: string; @@ -165,18 +166,16 @@ class ExtensionBlueprintImpl< output?: TOutput; params?: TParams; config?: { - schema: { - [key in keyof TExtensionConfigSchema]: ( - zImpl: typeof z, - ) => TExtensionConfigSchema[key]; - }; + schema: TExtensionConfigSchema; }; factory?( originalFactory: ( params: TParams, context?: { config?: { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; }; inputs?: Expand>; }, @@ -185,10 +184,10 @@ class ExtensionBlueprintImpl< node: AppNode; config: { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; } & { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; }; inputs: Expand>; }, @@ -196,23 +195,16 @@ class ExtensionBlueprintImpl< }): ExtensionDefinition< { [key in keyof TExtensionConfigSchema]: z.infer< - TExtensionConfigSchema[key] + ReturnType >; } & { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer>; } > { const schema = { ...this.options.config?.schema, ...args.config?.schema, - } as { - [key in keyof TConfigSchema]: (zImpl: typeof z) => TConfigSchema[key]; - } & { - [key in keyof TExtensionConfigSchema]: ( - zImpl: typeof z, - ) => TExtensionConfigSchema[key]; - }; - + } as TConfigSchema & TExtensionConfigSchema; return createExtension({ kind: this.options.kind, namespace: args.namespace ?? this.options.namespace, @@ -229,7 +221,9 @@ class ExtensionBlueprintImpl< innerParams: TParams, innerContext?: { config?: { - [key in keyof TConfigSchema]: z.infer; + [key in keyof TConfigSchema]: z.infer< + ReturnType + >; }; inputs?: Expand>; }, @@ -268,7 +262,7 @@ export function createExtensionBlueprint< TParams, TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, - TConfigSchema extends { [key in string]: z.ZodType }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, TDataRefs extends AnyExtensionDataMap = never, >( options: CreateExtensionBlueprintOptions< @@ -282,8 +276,20 @@ export function createExtensionBlueprint< TParams, TInputs, TOutput, - { [key in keyof TConfigSchema]: z.infer }, + string extends keyof TConfigSchema + ? {} + : { [key in keyof TConfigSchema]: z.infer> }, TDataRefs > { - return new ExtensionBlueprintImpl(options); + return new ExtensionBlueprintImpl(options) as ExtensionBlueprint< + TParams, + TInputs, + TOutput, + string extends keyof TConfigSchema + ? {} + : { + [key in keyof TConfigSchema]: z.infer>; + }, + TDataRefs + >; }