diff --git a/.changeset/every-schools-find.md b/.changeset/every-schools-find.md new file mode 100644 index 0000000000..b4cb15bf52 --- /dev/null +++ b/.changeset/every-schools-find.md @@ -0,0 +1,20 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-signals': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +'@backstage/plugin-app': patch +--- + +Internal update to use the new variant of `ApiBlueprint`. diff --git a/.changeset/odd-beans-sell.md b/.changeset/odd-beans-sell.md new file mode 100644 index 0000000000..50b8ec0f89 --- /dev/null +++ b/.changeset/odd-beans-sell.md @@ -0,0 +1,40 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `ApiBlueprint` has been updated to use the new advanced type parameters through the new `defineParams` blueprint option. This is an immediate breaking change that requires all existing usages of `ApiBlueprint` to switch to the new callback format. Existing extensions created with the old format are still compatible with the latest version of the plugin API however, meaning that this does not break existing plugins. + +To update existing usages of `ApiBlueprint`, you remove the outer level of the `params` object and replace `createApiFactory(...)` with `define => define(...)`. + +For example, the following old usage: + +```ts +ApiBlueprint.make({ + name: 'error', + params: { + factory: createApiFactory({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }) + }, +}) +``` + +is migrated to the following: + +```ts +ApiBlueprint.make({ + name: 'error', + params: define => + define({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }), +}) +``` diff --git a/.changeset/quiet-parks-cheer.md b/.changeset/quiet-parks-cheer.md new file mode 100644 index 0000000000..31e1ff8435 --- /dev/null +++ b/.changeset/quiet-parks-cheer.md @@ -0,0 +1,42 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +Added support for advanced parameter types in extension blueprints. The primary purpose of this is to allow extension authors to use type inference in the definition of the blueprint parameters. This often removes the need for extra imports and improves discoverability of blueprint parameters. + +This feature is introduced through the new `defineParams` option of `createExtensionBlueprint`, along with accompanying `createExtensionBlueprintParams` function to help implement the new format. + +The following is an example of how to create an extension blueprint that uses the new option: + +```ts +const ExampleBlueprint = createExtensionBlueprint({ + kind: 'example', + attachTo: { id: 'example', input: 'example' }, + output: [exampleComponentDataRef, exampleFetcherDataRef], + defineParams(params: { + component(props: ExampleProps): JSX.Element | null; + fetcher(options: FetchOptions): Promise>; + }) { + // The returned params must be wrapped with `createExtensionBlueprintParams` + return createExtensionBlueprintParams(params); + }, + *factory(params) { + // These params are now inferred + yield exampleComponentDataRef(params.component); + yield exampleFetcherDataRef(params.fetcher); + }, +}); +``` + +Usage of the above example looks as follows: + +```ts +const example = ExampleBlueprint.make({ + params: define => define({ + component: ..., + fetcher: ..., + }), +}); +``` + +This `define => define()` is also known as the "callback syntax" and is required if a blueprint is created with the new `defineParams` option. The callback syntax can also optionally be used for other blueprints too, which means that it is not a breaking change to remove the `defineParams` option, as long as the external parameter types remain compatible. diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index 081497655e..f7d019d6bc 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -60,6 +60,39 @@ When using `makeWithOverrides`, we no longer pass the blueprint parameters direc Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method. +### Creating an extension from a blueprint with advanced parameter types + +Some blueprints may be defined with something known as "advanced parameter types". This is a feature that enables type inference and transform of the blueprint parameters, and the way that you pass the parameters look a little bit different. Rather than passing the parameters directly, they are instead passed as a callback function of the form `define => define()`. + +An example of a blueprint that uses advanced parameter types is the `ApiBlueprint` blueprint. Using it to create a simple implementation for the `AlertApi` might look like this: + +```ts +const alertApiBlueprint = ApiBlueprint.make({ + params: define => + define({ + api: alertApiRef, + deps: {}, + factory: () => new MyAlertApi(), + }), +}); +``` + +This also works with `makeWithOverrides`, where the define callback is passed as the first argument to the original factory: + +```ts +const alertApiBlueprint = ApiBlueprint.makeWithOverrides({ + factory(originalFactory, { config }) { + return originalFactory(define => + define({ + api: alertApiRef, + deps: {}, + factory: () => new MyAlertApi(config), + }), + ); + }, +}); +``` + ## Creating an extension blueprint To create a new extension blueprint, you use the `createExtensionBlueprint` function. At the surface it is very similar to `createExtension`, but with a few key differences. Firstly you must provide a `kind` option, which will be the kind of all extensions created with the blueprint. See the [naming patterns section](./50-naming-patterns.md) for more information about how to select a good extension kind. Secondly, the `factory` function has a new signature where the first parameter is the blueprint parameters, and the second is the factory context. And finally, rather than returning an extension, `createExtensionBlueprint` returns a blueprint object with the `make` method and friends, which is used as is described above. @@ -99,6 +132,41 @@ This is of course a quite bare-bones example blueprint, but still a very real ex Most of the options provided to `createExtensionBlueprint` can be overridden when using `makeWithOverrides` to create an extension from the blueprint. These overrides work the same way as [extension overrides](./25-extension-overrides.md), and we defer to that documentation for more information on how overrides work. +### Creating an extension blueprint with advanced parameter types + +In some cases you may want to use inferred type parameters in the definition of the blueprint parameters. For this you need to use something known as "advanced parameter types". This is a feature that enables type inference and transform of the blueprint parameters, and the way you define the parameter type is a bit different. Rather than defining the type of the parameters as part of the factory function, you instead provide a separate `defineParams` options. This is a function that takes the parameters as a single argument, and must then return the parameters wrapped with the `createExtensionBlueprintParams` function. + +The following is an example of how one might define a blueprint where the parameters make use of inferred types: + +```ts +export interface MyWidgetBlueprintParams { + defaultOptions: T; + elementFactory(options: T): JSX.Element; +} + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + output: [coreExtensionData.reactElement], + defineParams(params: MyWidgetBlueprintParams) { + return createExtensionBlueprintParams(params); + }, + // Note that we no longer define the parameters type here, they are inferred from the defineParams function + factory(params) { + return [ + coreExtensionData.reactElement( + , + ), + ]; + }, +}); +``` + +If you happen to ask yourself, "why can't I just define type parameters on the factory function instead?", this is a limitation in the TypeScript type system. We could technically support that in the blueprint definition, but there would be no way for that logic to be carried forward to the blueprint `.make` and `.makeWithOverrides` methods. + ### Blueprint-specific extension data references In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions. diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 6b1332834a..41b0a43b15 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -199,13 +199,12 @@ import { ApiBlueprint } from '@backstage/frontend-plugin-api'; const scmIntegrationsApi = ApiBlueprint.make({ name: 'scm-integrations', - params: { - factory: createApiFactory({ + params: define => + define({ api: scmIntegrationsApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), - }, }); ``` diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index e9d6f1cbe2..ecabf0e2ef 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -154,19 +154,18 @@ export function ExamplePage() { ``` ```tsx title="in src/plugin.ts - Registering a factory for our API" -import { createApiFactory, ApiBlueprint } from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; import { exampleApiRef, DefaultExampleApi } from './api'; // highlight-add-start const exampleApi = ApiBlueprint.make({ name: 'example', - params: { - factory: createApiFactory({ + params: define => + define({ api: exampleApiRef, deps: {}, factory: () => new DefaultExampleApi(), }), - }, }); // highlight-add-end diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 483ebf68f8..2215c548ae 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -205,22 +205,17 @@ The major changes we'll make are The end result, after simplifying imports and cleaning up a bit, might look like this: ```tsx title="in @internal/plugin-example" -import { - storageApiRef, - createApiFactory, - ApiBlueprint, -} from '@backstage/frontend-plugin-api'; +import { storageApiRef, ApiBlueprint } from '@backstage/frontend-plugin-api'; import { workApiRef } from '@internal/plugin-example-react'; import { WorkImpl } from './WorkImpl'; const exampleWorkApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: workApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), - }, }); ``` diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index d02b74cbf6..d541173541 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -45,7 +45,6 @@ The plugin itself now wants to provide this API and its default implementation, ```tsx title="in @internal/plugin-example" import { ApiBlueprint, - createApiFactory, createFrontendPlugin, storageApiRef, StorageApi, @@ -63,15 +62,14 @@ class WorkImpl implements WorkApi { const workApi = ApiBlueprint.make({ name: 'work', - params: { - factory: createApiFactory({ + params: define => + define({ api: workApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => { return new WorkImpl({ storageApi }); }, }), - }, }); /** diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index d5ea537284..4652125d8e 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -46,14 +46,13 @@ Your utility APIs can depend on other utility APIs in their factories. You do th import { configApiRef, ApiBlueprint, - createApiFactory, discoveryApiRef, } from '@backstage/frontend-plugin-api'; import { MyApiImpl } from './MyApiImpl'; const myApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: myApiRef, deps: { configApi: configApiRef, @@ -63,7 +62,6 @@ const myApi = ApiBlueprint.make({ return new MyApiImpl({ configApi, discoveryApi }); }, }), - }, }); ``` diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 425f3684b6..e0705df307 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -310,7 +310,7 @@ export function collectLegacyRoutes( ...Array.from(plugin.getApis()).map(factory => ApiBlueprint.make({ name: factory.api.id, - params: { factory }, + params: define => define(factory), }), ), ], diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 8a48042934..5742c80487 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -232,31 +232,32 @@ describe('convertLegacyApp', () => { const catalogOverride = catalogPlugin.withOverrides({ extensions: [ catalogPlugin.getExtension('api:catalog').override({ - params: { - factory: createApiFactory( - catalogApiRef, - catalogApiMock({ - entities: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'test', - metadata: { - name: 'x', + params: define => + define({ + api: catalogApiRef, + deps: {}, + factory: () => + catalogApiMock({ + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'test', + metadata: { + name: 'x', + }, + spec: {}, }, - spec: {}, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'other', - metadata: { - name: 'x', + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'other', + metadata: { + name: 'x', + }, + spec: {}, }, - spec: {}, - }, - ], - }), - ), - }, + ], + }), + }), }), ], }); diff --git a/packages/core-compat-api/src/convertLegacyAppOptions.tsx b/packages/core-compat-api/src/convertLegacyAppOptions.tsx index 56aec7c1c5..c98875464c 100644 --- a/packages/core-compat-api/src/convertLegacyAppOptions.tsx +++ b/packages/core-compat-api/src/convertLegacyAppOptions.tsx @@ -74,7 +74,10 @@ export function convertLegacyAppOptions( new Map(allApis.map(api => [api.api.id, api])).values(), ); const extensions: ExtensionDefinition[] = deduplicatedApis.map(factory => - ApiBlueprint.make({ name: factory.api.id, params: { factory } }), + ApiBlueprint.make({ + name: factory.api.id, + params: define => define(factory), + }), ); if (icons) { diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts index bf315563ef..952b50a836 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.ts +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -29,7 +29,10 @@ export function convertLegacyPlugin( options: { extensions: ExtensionDefinition[] }, ): NewBackstagePlugin { const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory => - ApiBlueprint.make({ name: factory.api.id, params: { factory } }), + ApiBlueprint.make({ + name: factory.api.id, + params: define => define(factory), + }), ); return createFrontendPlugin({ pluginId: legacyPlugin.getId(), diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 7c6bb7958b..c9ff9fabd5 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -31,11 +31,7 @@ import { import { screen, render } from '@testing-library/react'; import { createSpecializedApp } from './createSpecializedApp'; import { mockApis, TestApiRegistry } from '@backstage/test-utils'; -import { - configApiRef, - createApiFactory, - featureFlagsApiRef, -} from '@backstage/core-plugin-api'; +import { configApiRef, featureFlagsApiRef } from '@backstage/core-plugin-api'; import { MemoryRouter } from 'react-router-dom'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; import { Fragment } from 'react'; @@ -148,16 +144,20 @@ describe('createSpecializedApp', () => { ], }), ApiBlueprint.make({ - params: { - factory: createApiFactory(featureFlagsApiRef, { - registerFlag(flag) { - flags.push(flag); - }, - getRegisteredFlags() { - return flags; - }, - } as typeof featureFlagsApiRef.T), - }, + params: define => + define({ + api: featureFlagsApiRef, + deps: {}, + factory: () => + ({ + registerFlag(flag) { + flags.push(flag); + }, + getRegisteredFlags() { + return flags; + }, + } as typeof featureFlagsApiRef.T), + }), }), ], }), @@ -253,15 +253,14 @@ describe('createSpecializedApp', () => { pluginId: 'first', extensions: [ ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: analyticsApiRef, deps: {}, factory: () => { throw new Error('BROKEN'); }, }), - }, }), ], }), @@ -295,13 +294,12 @@ describe('createSpecializedApp', () => { }, }), ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: analyticsApiRef, deps: {}, factory: mockAnalyticsApi, }), - }, }), ], }), diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index cd8f3d02ce..8ae1960221 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -179,9 +179,13 @@ export type AnyRoutes = { export const ApiBlueprint: ExtensionBlueprint<{ kind: 'api'; name: undefined; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; output: ConfigurableExtensionDataRef; inputs: {}; config: {}; @@ -519,7 +523,7 @@ export function createExtension< // @public export function createExtensionBlueprint< - TParams extends object, + TParams extends object | ExtensionBlueprintParamsDefiner, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -575,7 +579,7 @@ export function createExtensionBlueprint< export type CreateExtensionBlueprintOptions< TKind extends string, TName extends string | undefined, - TParams, + TParams extends object | ExtensionBlueprintParamsDefiner, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -603,8 +607,13 @@ export type CreateExtensionBlueprintOptions< config?: { schema: TConfigSchema; }; + defineParams?: TParams extends ExtensionBlueprintParamsDefiner + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; factory( - params: TParams, + params: TParams extends ExtensionBlueprintParamsDefiner + ? ReturnType['T'] + : TParams, context: { node: AppNode; apis: ApiHolder; @@ -617,6 +626,11 @@ export type CreateExtensionBlueprintOptions< dataRefs?: TDataRefs; } & VerifyExtensionFactoryOutput; +// @public +export function createExtensionBlueprintParams( + params: T, +): ExtensionBlueprintParams; + // @public (undocumented) export function createExtensionDataRef(): { with(options: { @@ -911,11 +925,18 @@ export interface ExtensionBlueprint< // (undocumented) dataRefs: T['dataRefs']; // (undocumented) - make(args: { + make< + TNewName extends string | undefined, + TParamsInput extends AnyParamsInput_2>, + >(args: { name?: TNewName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; - params: T['params']; + params: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: define => define() })`' + : TParamsInput; }): ExtensionDefinition<{ kind: T['kind']; name: string | undefined extends TNewName ? T['name'] : TNewName; @@ -957,8 +978,14 @@ export interface ExtensionBlueprint< }; }; factory( - originalFactory: ( - params: T['params'], + originalFactory: < + TParamsInput extends AnyParamsInput_2>, + >( + params: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : TParamsInput, context?: { config?: T['config']; inputs?: ResolveInputValueOverrides>; @@ -1012,7 +1039,7 @@ export interface ExtensionBlueprint< export type ExtensionBlueprintParameters = { kind: string; name?: string; - params?: object; + params?: object | ExtensionBlueprintParamsDefiner; configInput?: { [K in string]: any; }; @@ -1034,6 +1061,18 @@ export type ExtensionBlueprintParameters = { }; }; +// @public +export type ExtensionBlueprintParams = { + $$type: '@backstage/BlueprintParams'; + T: T; +}; + +// @public +export type ExtensionBlueprintParamsDefiner< + TParams extends object = object, + TInput = any, +> = (params: TInput) => ExtensionBlueprintParams; + // @public (undocumented) export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_2.Element; @@ -1130,6 +1169,7 @@ export type ExtensionDefinition< } >; }, + TParamsInput extends AnyParamsInput>, >( args: Expand< { @@ -1147,7 +1187,11 @@ export type ExtensionDefinition< }; }; factory?( - originalFactory: ( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( context?: Expand< { config?: T['config']; @@ -1155,7 +1199,11 @@ export type ExtensionDefinition< } & ([T['params']] extends [never] ? {} : { - params?: Partial; + params?: TFactoryParamsReturn extends ExtensionBlueprintParamsDefiner + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : Partial; }) >, ) => ExtensionDataContainer>, @@ -1173,7 +1221,11 @@ export type ExtensionDefinition< } & ([T['params']] extends [never] ? {} : { - params?: Partial; + params?: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : Partial; }) > & VerifyExtensionFactoryOutput< @@ -1223,7 +1275,7 @@ export type ExtensionDefinitionParameters = { } >; }; - params?: object; + params?: object | ExtensionBlueprintParamsDefiner; }; // @public (undocumented) diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 862f802810..6eafcf2556 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -16,21 +16,19 @@ import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; -import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '@backstage/core-plugin-api'; describe('ApiBlueprint', () => { it('should create an extension with sensible defaults', () => { const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = createApiFactory({ - api, - deps: {}, - factory: () => ({ foo: 'bar' }), - }); const extension = ApiBlueprint.make({ - params: { - factory, - }, + params: define => + define({ + api, + deps: {}, + factory: () => ({ foo: 'bar' }), + }), name: 'test', }); @@ -58,6 +56,97 @@ describe('ApiBlueprint', () => { `); }); + it('should properly type the API factory', () => { + const fooApi = createApiRef<{ foo: string }>({ id: 'foo' }); + const barApi = createApiRef<{ bar: string }>({ id: 'bar' }); + + expect('test').not.toBe('failing without assertions'); + + ApiBlueprint.make({ + params: define => + define({ api: fooApi, deps: {}, factory: () => ({ foo: 'foo' }) }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: {}, + // @ts-expect-error missing property + factory: () => ({}), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: {}, + // @ts-expect-error wrong property + factory: () => ({ + bar: 'bar', + }), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: {}, + factory: () => ({ + // @ts-expect-error wrong type + foo: 1, + }), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ foo: bar.bar }), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ + // @ts-expect-error not an available property + foo: bar.foo, + }), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ + // @ts-expect-error not an available property + foo: bar.foo, + }), + }), + }); + + ApiBlueprint.make({ + params: define => + define({ + api: fooApi, + deps: {}, + factory: ({ bar }) => ({ + // @ts-expect-error unknown dep + foo: bar.bar, + }), + }), + }); + }); + it('should create an extension with custom factory', () => { const api = createApiRef<{ foo: string }>({ id: 'test' }); const factory = jest.fn(() => ({ foo: 'bar' })); @@ -73,13 +162,7 @@ describe('ApiBlueprint', () => { }, name: api.id, factory(originalFactory, { config: _config, inputs: _inputs }) { - return originalFactory({ - factory: createApiFactory({ - api, - deps: {}, - factory, - }), - }); + return originalFactory(define => define({ api, deps: {}, factory })); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index 1f28a3cf54..a066a362d9 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -14,8 +14,9 @@ * limitations under the License. */ +import { AnyApiFactory, ApiFactory } from '../apis/system'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { createExtensionBlueprintParams } from '../wiring/createExtensionBlueprint'; const factoryDataRef = createExtensionDataRef().with({ id: 'core.api.factory', @@ -33,7 +34,14 @@ export const ApiBlueprint = createExtensionBlueprint({ dataRefs: { factory: factoryDataRef, }, - *factory(params: { factory: AnyApiFactory }) { - yield factoryDataRef(params.factory); + defineParams: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => createExtensionBlueprintParams(params as AnyApiFactory), + *factory(params) { + yield factoryDataRef(params); }, }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 04d7f15240..8ade3d63c9 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -30,6 +30,7 @@ import { z } from 'zod'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDataContainer } from './types'; +import { ExtensionBlueprintParamsDefiner } from './createExtensionBlueprint'; /** * This symbol is used to pass parameter overrides from the extension override to the blueprint factory @@ -161,9 +162,23 @@ export type ExtensionDefinitionParameters = { { optional: boolean; singleton: boolean } >; }; - params?: object; + params?: object | ExtensionBlueprintParamsDefiner; }; +/** + * Same as the one in `createExtensionBlueprint`, but with `ParamsFactory` inlined. + * It can't be exported because it breaks API reports. + * @ignore + */ +type AnyParamsInput = + TParams extends ExtensionBlueprintParamsDefiner + ? IParams | ((define: TParams) => ReturnType) + : + | TParams + | (( + define: ExtensionBlueprintParamsDefiner, + ) => ReturnType>); + /** @public */ export type ExtensionDefinition< T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, @@ -183,6 +198,7 @@ export type ExtensionDefinition< { optional: boolean; singleton: boolean } >; }, + TParamsInput extends AnyParamsInput>, >( args: Expand< { @@ -200,14 +216,24 @@ export type ExtensionDefinition< }; }; factory?( - originalFactory: ( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( context?: Expand< { config?: T['config']; inputs?: ResolveInputValueOverrides>; } & ([T['params']] extends [never] ? {} - : { params?: Partial }) + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintParamsDefiner + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : Partial; + }) >, ) => ExtensionDataContainer>, context: { @@ -223,7 +249,13 @@ export type ExtensionDefinition< ): Iterable; } & ([T['params']] extends [never] ? {} - : { params?: Partial }) + : { + params?: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : Partial; + }) > & VerifyExtensionFactoryOutput< AnyExtensionDataRef extends UNewOutput diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 5a44dc4b55..e0b43382b7 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -15,7 +15,11 @@ */ import { coreExtensionData } from './coreExtensionData'; -import { createExtensionBlueprint } from './createExtensionBlueprint'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + ExtensionBlueprintParams, +} from './createExtensionBlueprint'; import { createExtensionTester, renderInTestApp, @@ -876,6 +880,21 @@ describe('createExtensionBlueprint', () => { test2: 'orig-2', }); + const extensionDef = blueprint.make({ + // Using define is optional in this case + params: define => + define({ + test1: 'orig-1', + test2: 'orig-2', + }), + }); + + expect(getOutputs(extensionDef)).toEqual({ + test1: 'orig-1', + test2: 'orig-2', + }); + + // Plain override expect( getOutputs( extension.override({ @@ -896,6 +915,8 @@ describe('createExtensionBlueprint', () => { extension.override({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }), ), @@ -904,6 +925,58 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override with original define + expect( + getOutputs( + extensionDef.override({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override with define + expect( + getOutputs( + extension.override({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override with define with original define + expect( + getOutputs( + extensionDef.override({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect( getOutputs( extension.override({ @@ -930,6 +1003,8 @@ describe('createExtensionBlueprint', () => { return origFactory({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }); }, @@ -940,6 +1015,70 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override via factory with original define + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }); + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override via factory with define + expect( + getOutputs( + extension.override({ + factory(origFactory) { + return origFactory({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory with define with original define + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect(() => getOutputs( extension.override({ @@ -991,6 +1130,21 @@ describe('createExtensionBlueprint', () => { test2: 'orig-2', }); + const extensionDef = blueprint.make({ + // Using define is optional in this case + params: define => + define({ + test1: 'orig-1', + test2: 'orig-2', + }), + }); + + expect(getOutputs(extensionDef)).toEqual({ + test1: 'orig-1', + test2: 'orig-2', + }); + + // Plain override expect( getOutputs( extension.override({ @@ -1011,6 +1165,8 @@ describe('createExtensionBlueprint', () => { extension.override({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }), ), @@ -1019,6 +1175,59 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override with original define + expect( + getOutputs( + extensionDef.override({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override with define + expect( + getOutputs( + extension.override({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override with define with original define + expect( + getOutputs( + extensionDef.override({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory expect( getOutputs( extension.override({ @@ -1045,6 +1254,8 @@ describe('createExtensionBlueprint', () => { return origFactory({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }); }, @@ -1055,6 +1266,70 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override via factory with original define + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }); + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override via factory with define + expect( + getOutputs( + extension.override({ + factory(origFactory) { + return origFactory({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory with define with original define + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: define => + define({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect(() => getOutputs( extension.override({ @@ -1069,4 +1344,272 @@ describe('createExtensionBlueprint', () => { ), ).toThrow('Refused to override params and factory at the same time'); }); + + describe('with advanced parameter types', () => { + const testDataRef = createExtensionDataRef().with({ id: 'test' }); + + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams(params: { + a: A; + b: B; + }) { + return createExtensionBlueprintParams(params); + }, + factory(params) { + return [testDataRef(`${params.a} ${params.b}`)]; + }, + }); + + it('should allow creation of extension blueprints', () => { + TestExtensionBlueprint.make({ + // @ts-expect-error not using define func + params: { + a: 'x', + b: 'y', + }, + }); + + TestExtensionBlueprint.make({ + params: define => + define({ + a: 'x', + // @ts-expect-error b doesn't match a + b: 'y', + }), + }); + + TestExtensionBlueprint.make({ + params: define => + define({ + a: 'x', + b: 'x', + // @ts-expect-error extra param + c: 'y', + }), + }); + + const extension = TestExtensionBlueprint.make({ + params: define => + define({ + a: 'x', + b: 'x', + }), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + T: undefined, + attachTo: { + id: 'test', + input: 'default', + }, + configSchema: undefined, + disabled: false, + inputs: {}, + kind: 'test-extension', + name: undefined, + namespace: undefined, + output: [testDataRef], + factory: expect.any(Function), + toString: expect.any(Function), + override: expect.any(Function), + version: 'v2', + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe('x x'); + + extension.override({ + // @ts-expect-error not using define func + params: { + a: 'z', + b: 'w', + }, + }); + + extension.override({ + params: define => + define({ + a: 'z', + // @ts-expect-error b doesn't match a + b: 'w', + }), + }); + + extension.override({ + params: define => + define({ + a: 'z', + b: 'z', + // @ts-expect-error extra param + c: 'w', + }), + }); + + const override = extension.override({ + params: define => + define({ + a: 'z', + b: 'z', + }), + }); + + expect(createExtensionTester(override).get(testDataRef)).toBe('z z'); + }); + + it('should allow overriding of the default factory', () => { + TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + // @ts-expect-error not using define func + return originalFactory({ + a: 'x', + b: 'y', + }); + }, + }); + + TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + return originalFactory(define => + define({ + a: 'x', + // @ts-expect-error b doesn't match a + b: 'y', + }), + ); + }, + }); + + const extension = TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + return originalFactory(define => + define({ + a: 'x', + b: 'x', + }), + ); + }, + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + T: undefined, + attachTo: { + id: 'test', + input: 'default', + }, + configSchema: undefined, + disabled: false, + inputs: {}, + kind: 'test-extension', + name: undefined, + namespace: undefined, + output: [testDataRef], + factory: expect.any(Function), + toString: expect.any(Function), + override: expect.any(Function), + version: 'v2', + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe('x x'); + + extension.override({ + // @ts-expect-error not using define func + params: { + a: 'z', + b: 'w', + }, + }); + + extension.override({ + params: define => + define({ + a: 'z', + // @ts-expect-error b doesn't match a + b: 'w', + }), + }); + + const override = extension.override({ + params: define => + define({ + a: 'z', + b: 'z', + }), + }); + + expect(createExtensionTester(override).get(testDataRef)).toBe('z z'); + }); + + it('should allow the params definer to transform the params', () => { + const TestTransformExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams(params: { a: number; b: number }) { + return createExtensionBlueprintParams({ + x: params.a + 1, + y: params.b + 1, + }); + }, + factory(params) { + return [testDataRef(`${params.x} ${params.y}`)]; + }, + }); + + const extension = TestTransformExtensionBlueprint.make({ + params: define => + define({ + a: 0, + b: 10, + }), + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe(`1 11`); + + expect( + createExtensionTester( + extension.override({ + params: define => + define({ + a: 20, + b: 30, + }), + }), + ).get(testDataRef), + ).toBe(`21 31`); + }); + + it('should support overloads', () => { + const TestTransformExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams: (params => createExtensionBlueprintParams(params)) as { + (params: { x: 1 }): ExtensionBlueprintParams<{ x: number }>; + (params: { x: 2 }): ExtensionBlueprintParams<{ x: number }>; + }, + factory(params) { + return [testDataRef(`x: ${params.x}`)]; + }, + }); + + const extension = TestTransformExtensionBlueprint.make({ + params: define => define({ x: 1 }), + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe(`x: 1`); + + TestTransformExtensionBlueprint.make({ + params: define => define({ x: 2 }), + }); + + TestTransformExtensionBlueprint.make({ + // @ts-expect-error doesn't match any overload + params: define => define({ x: 3 }), + }); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 57848a7980..ced7eabc87 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -16,6 +16,7 @@ import { ApiHolder, AppNode } from '../apis'; import { Expand } from '@backstage/types'; +import { OpaqueType } from '@internal/opaque'; import { ExtensionAttachToSpec, ExtensionDefinition, @@ -37,13 +38,73 @@ import { } from './resolveInputOverrides'; import { ExtensionDataContainer } from './types'; +/** + * A function used to define a parameter mapping function in order to facilitate + * advanced parameter typing for extension blueprints. + * + * @remarks + * + * This function is primarily intended to enable the use of inferred type + * parameters for blueprint params, but it can also be used to transoform the + * params before they are handed ot the blueprint. + * + * The function must return an object created with + * {@link createExtensionBlueprintParams}. + * + * @public + */ +export type ExtensionBlueprintParamsDefiner< + TParams extends object = object, + TInput = any, +> = (params: TInput) => ExtensionBlueprintParams; + +/** + * An opaque type that represents a set of parameters to be passed to a blueprint. + * + * @remarks + * + * Created with {@link createExtensionBlueprintParams}. + * + * @public + */ +export type ExtensionBlueprintParams = { + $$type: '@backstage/BlueprintParams'; + T: T; +}; + +const OpaqueBlueprintParams = OpaqueType.create<{ + public: ExtensionBlueprintParams; + versions: { + version: 'v1'; + params: object; + }; +}>({ + type: '@backstage/BlueprintParams', + versions: ['v1'], +}); + +/** + * Wraps a plain blueprint parameter object in an opaque {@link ExtensionBlueprintParams} object. + * + * This is used in the definition of the `defineParams` option of {@link ExtensionBlueprint}. + * + * @public + * @param params - The plain blueprint parameter object to wrap. + * @returns The wrapped blueprint parameter object. + */ +export function createExtensionBlueprintParams( + params: T, +): ExtensionBlueprintParams { + return OpaqueBlueprintParams.createInstance('v1', { T: null as any, params }); +} + /** * @public */ export type CreateExtensionBlueprintOptions< TKind extends string, TName extends string | undefined, - TParams, + TParams extends object | ExtensionBlueprintParamsDefiner, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -64,8 +125,51 @@ export type CreateExtensionBlueprintOptions< config?: { schema: TConfigSchema; }; + /** + * This option is used to further refine the blueprint params. When this + * option is used, the blueprint will require params to be passed in callback + * form. This function can both transform the params before they are handed to + * the blueprint factory, but importantly it also allows you to define + * inferred type parameters for your blueprint params. + * + * @example + * Blueprint definition with inferred type parameters: + * ```ts + * const ExampleBlueprint = createExtensionBlueprint({ + * kind: 'example', + * attachTo: { id: 'example', input: 'example' }, + * output: [exampleComponentDataRef, exampleFetcherDataRef], + * defineParams(params: { + * component(props: ExampleProps): JSX.Element | null + * fetcher(options: FetchOptions): Promise> + * }) { + * return createExtensionBlueprintParams(params); + * }, + * *factory(params) { + * yield exampleComponentDataRef(params.component) + * yield exampleFetcherDataRef(params.fetcher) + * }, + * }); + * ``` + * + * @example + * Usage of the above example blueprint: + * ```ts + * const example = ExampleBlueprint.make({ + * params: define => define({ + * component: ..., + * fetcher: ..., + * }), + * }); + * ``` + */ + defineParams?: TParams extends ExtensionBlueprintParamsDefiner + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; factory( - params: TParams, + params: TParams extends ExtensionBlueprintParamsDefiner + ? ReturnType['T'] + : TParams, context: { node: AppNode; apis: ApiHolder; @@ -83,7 +187,7 @@ export type CreateExtensionBlueprintOptions< export type ExtensionBlueprintParameters = { kind: string; name?: string; - params?: object; + params?: object | ExtensionBlueprintParamsDefiner; configInput?: { [K in string]: any }; config?: { [K in string]: any }; output?: AnyExtensionDataRef; @@ -96,19 +200,45 @@ export type ExtensionBlueprintParameters = { dataRefs?: { [name in string]: AnyExtensionDataRef }; }; +/** @ignore */ +type ParamsFactory = ( + define: TDefiner, +) => ReturnType; + +/** + * Represents any form of params input that can be passed to a blueprint. + * This also includes the invalid form of passing a plain params object to a blueprint that uses a definition callback. + * + * @ignore + */ +type AnyParamsInput = + TParams extends ExtensionBlueprintParamsDefiner + ? IParams | ParamsFactory + : + | TParams + | ParamsFactory>; + /** * @public */ export interface ExtensionBlueprint< + // TParamsMapper extends (params: any) => object, T extends ExtensionBlueprintParameters = ExtensionBlueprintParameters, > { dataRefs: T['dataRefs']; - make(args: { + make< + TNewName extends string | undefined, + TParamsInput extends AnyParamsInput>, + >(args: { name?: TNewName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; - params: T['params']; + params: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: define => define() })`' + : TParamsInput; }): ExtensionDefinition<{ kind: T['kind']; name: string | undefined extends TNewName ? T['name'] : TNewName; @@ -154,8 +284,14 @@ export interface ExtensionBlueprint< }; }; factory( - originalFactory: ( - params: T['params'], + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintParamsDefiner + ? TParamsInput + : T['params'] extends ExtensionBlueprintParamsDefiner + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(define => define())`' + : TParamsInput, context?: { config?: T['config']; inputs?: ResolveInputValueOverrides>; @@ -205,6 +341,76 @@ export interface ExtensionBlueprint< }>; } +function unwrapParamsFactory( + // Allow `Function` because `typeof === 'function'` allows it, but in practice this should always be a param factory + params: ParamsFactory | Function, + defineParams: ExtensionBlueprintParamsDefiner, + kind: string, +): TParams { + const paramDefinition = ( + params as ParamsFactory + )(defineParams); + try { + return OpaqueBlueprintParams.toInternal(paramDefinition).params as TParams; + } catch (e) { + throw new TypeError( + `Invalid invocation of blueprint with kind '${kind}', the parameter definition callback function did not return a valid parameter definition object; Caused by: ${e.message}`, + ); + } +} + +function unwrapParams( + params: object | ParamsFactory | string, + ctx: { node: AppNode; [ctxParamsSymbol]?: any }, + defineParams: ExtensionBlueprintParamsDefiner | undefined, + kind: string, +): TParams { + const overrideParams = ctx[ctxParamsSymbol] as + | object + | ParamsFactory + | undefined; + + if (defineParams) { + if (overrideParams) { + if (typeof overrideParams !== 'function') { + throw new TypeError( + `Invalid extension override of blueprint with kind '${kind}', the override params were passed as a plain object, but this blueprint requires them to be passed in callback form`, + ); + } + return unwrapParamsFactory(overrideParams, defineParams, kind); + } + + if (typeof params !== 'function') { + throw new TypeError( + `Invalid invocation of blueprint with kind '${kind}', the parameters where passed as a plain object, but this blueprint requires them to be passed in callback form`, + ); + } + return unwrapParamsFactory(params, defineParams, kind); + } + + const base = + typeof params === 'function' + ? unwrapParamsFactory( + params, + createExtensionBlueprintParams, + kind, + ) + : (params as TParams); + const overrides = + typeof overrideParams === 'function' + ? unwrapParamsFactory( + overrideParams, + createExtensionBlueprintParams, + kind, + ) + : (overrideParams as Partial); + + return { + ...base, + ...overrides, + }; +} + /** * A simpler replacement for wrapping up `createExtension` inside a kind or type. This allows for a cleaner API for creating * types and instances of those types. @@ -212,7 +418,7 @@ export interface ExtensionBlueprint< * @public */ export function createExtensionBlueprint< - TParams extends object, + TParams extends object | ExtensionBlueprintParamsDefiner, UOutput extends AnyExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< @@ -254,6 +460,10 @@ export function createExtensionBlueprint< >; dataRefs: TDataRefs; }> { + const defineParams = options.defineParams as + | ExtensionBlueprintParamsDefiner + | undefined; + return { dataRefs: options.dataRefs, make(args) { @@ -267,7 +477,7 @@ export function createExtensionBlueprint< config: options.config, factory: ctx => options.factory( - { ...args.params, ...(ctx as any)[ctxParamsSymbol] }, + unwrapParams(args.params, ctx, defineParams, options.kind), ctx, ) as Iterable>, }) as ExtensionDefinition; @@ -295,7 +505,7 @@ export function createExtensionBlueprint< (innerParams, innerContext) => { return createExtensionDataContainer( options.factory( - { ...innerParams, ...(ctx as any)[ctxParamsSymbol] }, + unwrapParams(innerParams, ctx, defineParams, options.kind), { apis, node, diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 24a4e1653f..0f170d949f 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -66,6 +66,9 @@ export { type CreateExtensionBlueprintOptions, type ExtensionBlueprint, type ExtensionBlueprintParameters, + type ExtensionBlueprintParams, + type ExtensionBlueprintParamsDefiner, createExtensionBlueprint, + createExtensionBlueprintParams, } from './createExtensionBlueprint'; export { type ResolveInputValueOverrides } from './resolveInputOverrides'; diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index b25a7b9375..a74d908024 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -113,6 +113,7 @@ describe('ResolveExtensionId', () => { kind: TKind; name: TName; output: any; + params: never; }>; const id1: 'k:ns' = {} as ResolveExtensionId< NamedExtension<'k', undefined>, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 7e771c6c2c..c3ff381fdc 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -116,6 +116,7 @@ export type ResolveExtensionId< > = TExtension extends ExtensionDefinition<{ kind: infer IKind extends string | undefined; name: infer IName extends string | undefined; + params: any; }> ? [string] extends [IKind | IName] ? never diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index b13fca4996..c383ea810c 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -6,11 +6,13 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -73,9 +75,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'entity-card:api-docs/consumed-apis': ExtensionDefinition<{ kind: 'entity-card'; diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 356328a61d..4ff73f0e26 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -20,7 +20,6 @@ import { ApiBlueprint, NavItemBlueprint, PageBlueprint, - createApiFactory, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; @@ -55,8 +54,8 @@ const apiDocsNavItem = NavItemBlueprint.make({ const apiDocsConfigApi = ApiBlueprint.make({ name: 'config', - params: { - factory: createApiFactory({ + params: define => + define({ api: apiDocsConfigRef, deps: {}, factory: () => { @@ -68,7 +67,6 @@ const apiDocsConfigApi = ApiBlueprint.make({ }; }, }), - }, }); const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 027b6365b5..15001d2961 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -6,10 +6,12 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { AppTheme } from '@backstage/frontend-plugin-api'; import { ComponentRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -222,9 +224,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/analytics': ExtensionDefinition<{ kind: 'api'; @@ -237,9 +243,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/app-language': ExtensionDefinition<{ kind: 'api'; @@ -252,9 +262,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/app-theme': ExtensionDefinition<{ config: {}; @@ -275,9 +289,13 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'app-theme'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/atlassian-auth': ExtensionDefinition<{ kind: 'api'; @@ -290,9 +308,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/bitbucket-auth': ExtensionDefinition<{ kind: 'api'; @@ -305,9 +327,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/bitbucket-server-auth': ExtensionDefinition<{ kind: 'api'; @@ -320,9 +346,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/components': ExtensionDefinition<{ config: {}; @@ -350,9 +380,13 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'components'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/dialog': ExtensionDefinition<{ kind: 'api'; @@ -365,9 +399,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/discovery': ExtensionDefinition<{ kind: 'api'; @@ -380,9 +418,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/error': ExtensionDefinition<{ kind: 'api'; @@ -395,9 +437,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/feature-flags': ExtensionDefinition<{ kind: 'api'; @@ -410,9 +456,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/fetch': ExtensionDefinition<{ kind: 'api'; @@ -425,9 +475,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/github-auth': ExtensionDefinition<{ kind: 'api'; @@ -440,9 +494,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/gitlab-auth': ExtensionDefinition<{ kind: 'api'; @@ -455,9 +513,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/google-auth': ExtensionDefinition<{ kind: 'api'; @@ -470,9 +532,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/icons': ExtensionDefinition<{ config: {}; @@ -499,9 +565,13 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'icons'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/microsoft-auth': ExtensionDefinition<{ kind: 'api'; @@ -514,9 +584,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/oauth-request': ExtensionDefinition<{ kind: 'api'; @@ -529,9 +603,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/okta-auth': ExtensionDefinition<{ kind: 'api'; @@ -544,9 +622,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/onelogin-auth': ExtensionDefinition<{ kind: 'api'; @@ -559,9 +641,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/permission': ExtensionDefinition<{ kind: 'api'; @@ -574,9 +660,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/scm-auth': ExtensionDefinition<{ kind: 'api'; @@ -589,9 +679,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/scm-integrations': ExtensionDefinition<{ kind: 'api'; @@ -604,9 +698,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/storage': ExtensionDefinition<{ kind: 'api'; @@ -619,9 +717,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/translations': ExtensionDefinition<{ config: {}; @@ -653,9 +755,13 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'translations'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/vmware-cloud-auth': ExtensionDefinition<{ kind: 'api'; @@ -668,9 +774,13 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'app-root-element:app/alert-display': ExtensionDefinition<{ config: { diff --git a/plugins/app/src/defaultApis.ts b/plugins/app/src/defaultApis.ts index 535be78e05..4da28b44a6 100644 --- a/plugins/app/src/defaultApis.ts +++ b/plugins/app/src/defaultApis.ts @@ -39,7 +39,6 @@ import { } from '../../../packages/core-app-api/src/apis/implementations'; import { - createApiFactory, alertApiRef, analyticsApiRef, errorApiRef, @@ -75,18 +74,17 @@ import { DefaultDialogApi } from './apis/DefaultDialogApi'; export const apis = [ ApiBlueprint.make({ name: 'dialog', - params: { - factory: createApiFactory({ + params: define => + define({ api: dialogApiRef, deps: {}, factory: () => new DefaultDialogApi(), }), - }, }), ApiBlueprint.make({ name: 'discovery', - params: { - factory: createApiFactory({ + params: define => + define({ api: discoveryApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => @@ -94,32 +92,29 @@ export const apis = [ `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`, ), }), - }, }), ApiBlueprint.make({ name: 'alert', - params: { - factory: createApiFactory({ + params: define => + define({ api: alertApiRef, deps: {}, factory: () => new AlertApiForwarder(), }), - }, }), ApiBlueprint.make({ name: 'analytics', - params: { - factory: createApiFactory({ + params: define => + define({ api: analyticsApiRef, deps: {}, factory: () => new NoOpAnalyticsApi(), }), - }, }), ApiBlueprint.make({ name: 'error', - params: { - factory: createApiFactory({ + params: define => + define({ api: errorApiRef, deps: { alertApi: alertApiRef }, factory: ({ alertApi }) => { @@ -128,22 +123,20 @@ export const apis = [ return errorApi; }, }), - }, }), ApiBlueprint.make({ name: 'storage', - params: { - factory: createApiFactory({ + params: define => + define({ api: storageApiRef, deps: { errorApi: errorApiRef }, factory: ({ errorApi }) => WebStorage.create({ errorApi }), }), - }, }), ApiBlueprint.make({ name: 'fetch', - params: { - factory: createApiFactory({ + params: define => + define({ api: fetchApiRef, deps: { configApi: configApiRef, @@ -164,22 +157,20 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'oauth-request', - params: { - factory: createApiFactory({ + params: define => + define({ api: oauthRequestApiRef, deps: {}, factory: () => new OAuthRequestManager(), }), - }, }), ApiBlueprint.make({ name: 'google-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: googleAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -194,12 +185,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'microsoft-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: microsoftAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -214,12 +204,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'github-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: githubAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -235,12 +224,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'okta-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: oktaAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -255,12 +243,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'gitlab-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: gitlabAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -275,12 +262,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'onelogin-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: oneloginAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -295,12 +281,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'bitbucket-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: bitbucketAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -316,12 +301,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'bitbucket-server-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: bitbucketServerAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -337,12 +321,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'atlassian-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: atlassianAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -358,12 +341,11 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'vmware-cloud-auth', - params: { - factory: createApiFactory({ + params: define => + define({ api: vmwareCloudAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -379,12 +361,11 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'permission', - params: { - factory: createApiFactory({ + params: define => + define({ api: permissionApiRef, deps: { discovery: discoveryApiRef, @@ -394,22 +375,18 @@ export const apis = [ factory: ({ config, discovery, identity }) => IdentityPermissionApi.create({ config, discovery, identity }), }), - }, }), ApiBlueprint.make({ name: 'scm-auth', - params: { - factory: ScmAuth.createDefaultApiFactory(), - }, + params: define => define(ScmAuth.createDefaultApiFactory()), }), ApiBlueprint.make({ name: 'scm-integrations', - params: { - factory: createApiFactory({ + params: define => + define({ api: scmIntegrationsApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), - }, }), ] as const; diff --git a/plugins/app/src/extensions/AppLanguageApi.ts b/plugins/app/src/extensions/AppLanguageApi.ts index 7a6c10db96..96d19a578a 100644 --- a/plugins/app/src/extensions/AppLanguageApi.ts +++ b/plugins/app/src/extensions/AppLanguageApi.ts @@ -17,14 +17,14 @@ // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi'; import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha'; -import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; export const AppLanguageApi = ApiBlueprint.make({ name: 'app-language', - params: { - factory: createApiFactory( - appLanguageApiRef, - AppLanguageSelector.createWithStorage(), - ), - }, + params: define => + define({ + api: appLanguageApiRef, + deps: {}, + factory: () => AppLanguageSelector.createWithStorage(), + }), }); diff --git a/plugins/app/src/extensions/AppThemeApi.tsx b/plugins/app/src/extensions/AppThemeApi.tsx index 2d35a50cd5..670c4504c6 100644 --- a/plugins/app/src/extensions/AppThemeApi.tsx +++ b/plugins/app/src/extensions/AppThemeApi.tsx @@ -24,7 +24,6 @@ import { createExtensionInput, ThemeBlueprint, ApiBlueprint, - createApiFactory, appThemeApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -41,14 +40,16 @@ export const AppThemeApi = ApiBlueprint.makeWithOverrides({ }), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - appThemeApiRef, - AppThemeSelector.createWithStorage( - inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), - ), - ), - }); + return originalFactory(define => + define({ + api: appThemeApiRef, + deps: {}, + factory: () => + AppThemeSelector.createWithStorage( + inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), + ), + }), + ); }, }); diff --git a/plugins/app/src/extensions/ComponentsApi.tsx b/plugins/app/src/extensions/ComponentsApi.tsx index b578429253..fa14d28b10 100644 --- a/plugins/app/src/extensions/ComponentsApi.tsx +++ b/plugins/app/src/extensions/ComponentsApi.tsx @@ -18,7 +18,6 @@ import { createComponentExtension, createExtensionInput, ApiBlueprint, - createApiFactory, componentsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -36,15 +35,17 @@ export const ComponentsApi = ApiBlueprint.makeWithOverrides({ ), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - componentsApiRef, - DefaultComponentsApi.fromComponents( - inputs.components.map(i => - i.get(createComponentExtension.componentDataRef), + return originalFactory(define => + define({ + api: componentsApiRef, + deps: {}, + factory: () => + DefaultComponentsApi.fromComponents( + inputs.components.map(i => + i.get(createComponentExtension.componentDataRef), + ), ), - ), - ), - }); + }), + ); }, }); diff --git a/plugins/app/src/extensions/FeatureFlagsApi.ts b/plugins/app/src/extensions/FeatureFlagsApi.ts index 40a44d1348..1a2eff0084 100644 --- a/plugins/app/src/extensions/FeatureFlagsApi.ts +++ b/plugins/app/src/extensions/FeatureFlagsApi.ts @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, featureFlagsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -27,12 +26,11 @@ import { LocalStorageFeatureFlags } from '../../../../packages/core-app-api/src/ */ export const FeatureFlagsApi = ApiBlueprint.make({ name: 'feature-flags', - params: { - // TODO: properly discovery feature flags, maybe rework the whole thing - factory: createApiFactory({ + params: define => + define({ + // TODO: properly discovery feature flags, maybe rework the whole thing api: featureFlagsApiRef, deps: {}, factory: () => new LocalStorageFeatureFlags(), }), - }, }); diff --git a/plugins/app/src/extensions/IconsApi.ts b/plugins/app/src/extensions/IconsApi.ts index e73a74132a..9457652870 100644 --- a/plugins/app/src/extensions/IconsApi.ts +++ b/plugins/app/src/extensions/IconsApi.ts @@ -18,7 +18,6 @@ import { createExtensionInput, IconBundleBlueprint, ApiBlueprint, - createApiFactory, iconsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -37,15 +36,17 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({ }), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - iconsApiRef, - new DefaultIconsApi( - inputs.icons - .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) - .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), - ), - ), - }); + return originalFactory(define => + define({ + api: iconsApiRef, + deps: {}, + factory: () => + new DefaultIconsApi( + inputs.icons + .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) + .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), + ), + }), + ); }, }); diff --git a/plugins/app/src/extensions/TranslationsApi.tsx b/plugins/app/src/extensions/TranslationsApi.tsx index ed7ab1abdb..d95c333e8b 100644 --- a/plugins/app/src/extensions/TranslationsApi.tsx +++ b/plugins/app/src/extensions/TranslationsApi.tsx @@ -16,7 +16,6 @@ import { ApiBlueprint, TranslationBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { @@ -39,8 +38,8 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({ ), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory({ + return originalFactory(define => + define({ api: translationApiRef, deps: { languageApi: appLanguageApiRef }, factory: ({ languageApi }) => @@ -51,6 +50,6 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({ ), }), }), - }); + ); }, }); diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 229c2ff10c..2e75f9481c 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/core-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -103,9 +105,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'page:catalog-import': ExtensionDefinition<{ kind: 'page'; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 17260acc14..6f96bfbed1 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -16,7 +16,6 @@ import { configApiRef, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; @@ -53,8 +52,8 @@ const catalogImportPage = PageBlueprint.make({ }); const catalogImportApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: catalogImportApiRef, deps: { discoveryApi: discoveryApiRef, @@ -81,7 +80,6 @@ const catalogImportApi = ApiBlueprint.make({ configApi, }), }), - }, }); /** @alpha */ diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 8b53bcb8ae..fa64ed329a 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -30,9 +32,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'nav-item:catalog-unprocessed-entities': ExtensionDefinition<{ kind: 'nav-item'; diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 34a40b23d7..cfe81eccc8 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -37,8 +36,8 @@ import { rootRouteRef } from '../routes'; /** @alpha */ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: catalogUnprocessedEntitiesApiRef, deps: { discoveryApi: discoveryApiRef, @@ -47,7 +46,6 @@ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new CatalogUnprocessedEntitiesClient(discoveryApi, fetchApi), }), - }, }); /** @alpha */ diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 81ebd10c3b..cfbbc5388e 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -6,6 +6,7 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; @@ -13,6 +14,7 @@ import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityContentLayoutProps } from '@backstage/plugin-catalog-react/alpha'; import { EntityContextMenuItemParams } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -146,9 +148,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:catalog/entity-presentation': ExtensionDefinition<{ kind: 'api'; @@ -161,9 +167,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:catalog/starred-entities': ExtensionDefinition<{ kind: 'api'; @@ -176,9 +186,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'catalog-filter:catalog/kind': ExtensionDefinition<{ config: { diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index d0f80c3aec..6bfa0a124b 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, discoveryApiRef, fetchApiRef, storageApiRef, @@ -33,8 +32,8 @@ import { } from '../apis'; export const catalogApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: catalogApiRef, deps: { discoveryApi: discoveryApiRef, @@ -43,31 +42,28 @@ export const catalogApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new CatalogClient({ discoveryApi, fetchApi }), }), - }, }); export const catalogStarredEntitiesApi = ApiBlueprint.make({ name: 'starred-entities', - params: { - factory: createApiFactory({ + params: define => + define({ api: starredEntitiesApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }), }), - }, }); export const entityPresentationApi = ApiBlueprint.make({ name: 'entity-presentation', - params: { - factory: createApiFactory({ + params: define => + define({ api: entityPresentationApiRef, deps: { catalogApiImp: catalogApiRef }, factory: ({ catalogApiImp }) => DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), }), - }, }); export default [catalogApi, catalogStarredEntitiesApi, entityPresentationApi]; diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 663c4be34d..2ede4d34b2 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -30,9 +32,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'nav-item:devtools': ExtensionDefinition<{ kind: 'nav-item'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 52caaf4786..d2f09521dd 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -34,8 +33,8 @@ import { rootRouteRef } from '../routes'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: devToolsApiRef, deps: { discoveryApi: discoveryApiRef, @@ -44,7 +43,6 @@ export const devToolsApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new DevToolsClient({ discoveryApi, fetchApi }), }), - }, }); /** @alpha */ diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index c7fcf8ab67..010918b029 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -31,9 +33,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'app-root-element:home/visit-listener': ExtensionDefinition<{ kind: 'app-root-element'; diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 0d5b0fb94c..d093f353a2 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -24,7 +24,6 @@ import { AppRootElementBlueprint, identityApiRef, storageApiRef, - createApiFactory, ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { compatWrapper } from '@backstage/core-compat-api'; @@ -79,8 +78,8 @@ const visitListenerAppRootElement = AppRootElementBlueprint.make({ const visitsApi = ApiBlueprint.make({ name: 'visits', - params: { - factory: createApiFactory({ + params: define => + define({ api: visitsApiRef, deps: { storageApi: storageApiRef, @@ -89,7 +88,6 @@ const visitsApi = ApiBlueprint.make({ factory: ({ storageApi, identityApi }) => VisitsStorageApi.create({ storageApi, identityApi }), }), - }, }); /** diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index e6cc199ae8..37bb084bd7 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -5,10 +5,12 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -33,9 +35,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/auth-providers': ExtensionDefinition<{ kind: 'api'; @@ -48,9 +54,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/cluster-link-formatter': ExtensionDefinition<{ kind: 'api'; @@ -63,9 +73,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/proxy': ExtensionDefinition<{ kind: 'api'; @@ -78,9 +92,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'entity-content:kubernetes/kubernetes': ExtensionDefinition<{ kind: 'entity-content'; diff --git a/plugins/kubernetes/src/alpha/apis.tsx b/plugins/kubernetes/src/alpha/apis.tsx index 082a2da6c7..c57e9a1482 100644 --- a/plugins/kubernetes/src/alpha/apis.tsx +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/frontend-plugin-api'; @@ -41,8 +40,8 @@ import { } from '@backstage/core-plugin-api'; export const kubernetesApiExtension = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: kubernetesApiRef, deps: { discoveryApi: discoveryApiRef, @@ -56,13 +55,12 @@ export const kubernetesApiExtension = ApiBlueprint.make({ kubernetesAuthProvidersApi, }), }), - }, }); export const kubernetesProxyApi = ApiBlueprint.make({ name: 'proxy', - params: { - factory: createApiFactory({ + params: define => + define({ api: kubernetesProxyApiRef, deps: { kubernetesApi: kubernetesApiRef, @@ -72,13 +70,12 @@ export const kubernetesProxyApi = ApiBlueprint.make({ kubernetesApi, }), }), - }, }); export const kubernetesAuthProvidersApi = ApiBlueprint.make({ name: 'auth-providers', - params: { - factory: createApiFactory({ + params: define => + define({ api: kubernetesAuthProvidersApiRef, deps: { gitlabAuthApi: gitlabAuthApiRef, @@ -109,13 +106,12 @@ export const kubernetesAuthProvidersApi = ApiBlueprint.make({ }); }, }), - }, }); export const kubernetesClusterLinkFormatterApi = ApiBlueprint.make({ name: 'cluster-link-formatter', - params: { - factory: createApiFactory({ + params: define => + define({ api: kubernetesClusterLinkFormatterApiRef, deps: { googleAuthApi: googleAuthApiRef }, factory: deps => { @@ -126,5 +122,4 @@ export const kubernetesClusterLinkFormatterApi = ApiBlueprint.make({ }); }, }), - }, }); diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 734dd0a201..b512d97ce3 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -29,9 +31,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'page:notifications': ExtensionDefinition<{ kind: 'page'; diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index 4424659593..68d0ccab8d 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -17,7 +17,6 @@ import { ApiBlueprint, PageBlueprint, - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -41,14 +40,13 @@ const page = PageBlueprint.make({ }); const api = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: notificationsApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory: ({ discoveryApi, fetchApi }) => new NotificationsClient({ discoveryApi, fetchApi }), }), - }, }); /** @alpha */ diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 2014bff9be..7de5c1b819 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -5,6 +5,7 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; @@ -12,6 +13,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; import { Dispatch } from 'react'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; @@ -218,9 +220,13 @@ export const formFieldsApi: ExtensionDefinition<{ }; kind: 'api'; name: 'form-fields'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; // @alpha @deprecated (undocumented) diff --git a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts b/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts index 7712b91a18..d66c27980d 100644 --- a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts +++ b/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { formFieldsApiRef } from './ref'; @@ -53,12 +52,12 @@ export const formFieldsApi = ApiBlueprint.makeWithOverrides({ e.get(FormFieldBlueprint.dataRefs.formFieldLoader), ); - return originalFactory({ - factory: createApiFactory({ + return originalFactory(define => + define({ api: formFieldsApiRef, deps: {}, factory: () => new DefaultScaffolderFormFieldsApi(formFieldLoaders), }), - }); + ); }, }); diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 509cfa8497..394ae081d6 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -5,11 +5,13 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -66,9 +68,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:scaffolder/form-decorators': ExtensionDefinition<{ config: {}; @@ -93,9 +99,13 @@ const _default: FrontendPlugin< }; kind: 'api'; name: 'form-decorators'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:scaffolder/form-fields': ExtensionDefinition<{ config: {}; @@ -120,9 +130,13 @@ const _default: FrontendPlugin< }; kind: 'api'; name: 'form-fields'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'entity-icon-link:scaffolder/launch-template': ExtensionDefinition<{ kind: 'entity-icon-link'; @@ -273,9 +287,13 @@ export const formDecoratorsApi: ExtensionDefinition<{ }; kind: 'api'; name: 'form-decorators'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; // @alpha (undocumented) diff --git a/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts b/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts index 41bf91dba0..4ae975ec5a 100644 --- a/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts +++ b/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { ScaffolderFormDecoratorsApi } from './types'; @@ -58,8 +57,8 @@ export const formDecoratorsApi = ApiBlueprint.makeWithOverrides({ e.get(FormDecoratorBlueprint.dataRefs.formDecoratorLoader), ); - return originalFactory({ - factory: createApiFactory({ + return originalFactory(define => + define({ api: formDecoratorsApiRef, deps: {}, factory: () => @@ -67,6 +66,6 @@ export const formDecoratorsApi = ApiBlueprint.makeWithOverrides({ decorators: formDecorators, }), }), - }); + ); }, }); diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index 7c82edc97f..351ad4ed56 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -22,7 +22,6 @@ import { NavItemBlueprint, PageBlueprint, ApiBlueprint, - createApiFactory, discoveryApiRef, fetchApiRef, identityApiRef, @@ -74,8 +73,8 @@ export const repoUrlPickerFormField = FormFieldBlueprint.make({ }); export const scaffolderApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: scaffolderApiRef, deps: { discoveryApi: discoveryApiRef, @@ -91,5 +90,4 @@ export const scaffolderApi = ApiBlueprint.make({ identityApi, }), }), - }, }); diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 3af0384d89..1edc5e678d 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/core-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -35,9 +37,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'nav-item:search': ExtensionDefinition<{ kind: 'nav-item'; @@ -145,9 +151,13 @@ export const searchApi: ExtensionDefinition<{ configInput: {}; output: ConfigurableExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; // @alpha (undocumented) diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 136f95f39e..5028a404d4 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -31,7 +31,6 @@ import { useApi, discoveryApiRef, fetchApiRef, - createApiFactory, } from '@backstage/core-plugin-api'; import { @@ -77,14 +76,13 @@ import { /** @alpha */ export const searchApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: searchApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory: ({ discoveryApi, fetchApi }) => new SearchClient({ discoveryApi, fetchApi }), }), - }, }); const useSearchPageStyles = makeStyles((theme: Theme) => ({ diff --git a/plugins/signals/report-alpha.api.md b/plugins/signals/report-alpha.api.md index 3fc89308c1..13091a9032 100644 --- a/plugins/signals/report-alpha.api.md +++ b/plugins/signals/report-alpha.api.md @@ -4,7 +4,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -25,9 +27,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'app-root-element:signals/signals-display': ExtensionDefinition<{ kind: 'app-root-element'; diff --git a/plugins/signals/src/alpha.tsx b/plugins/signals/src/alpha.tsx index 38955473e1..340425bebd 100644 --- a/plugins/signals/src/alpha.tsx +++ b/plugins/signals/src/alpha.tsx @@ -17,7 +17,6 @@ import { ApiBlueprint, AppRootElementBlueprint, - createApiFactory, createFrontendPlugin, discoveryApiRef, identityApiRef, @@ -28,8 +27,8 @@ import { SignalsDisplay } from './plugin'; import { compatWrapper } from '@backstage/core-compat-api'; const api = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: signalApiRef, deps: { identity: identityApiRef, @@ -42,7 +41,6 @@ const api = ApiBlueprint.make({ }); }, }), - }, }); const signalsDisplayAppRootElement = AppRootElementBlueprint.make({ diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index ce1d10a00c..fd63ec1daa 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -6,10 +6,12 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -46,9 +48,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:techdocs/storage': ExtensionDefinition<{ kind: 'api'; @@ -61,9 +67,13 @@ const _default: FrontendPlugin< {} >; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'empty-state:techdocs/entity-content': ExtensionDefinition<{ config: {}; diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx index 44205a480a..38dcd40dc4 100644 --- a/plugins/techdocs/src/alpha/index.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -26,7 +26,6 @@ import { } from '@backstage/frontend-plugin-api'; import { configApiRef, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; @@ -68,8 +67,8 @@ const techdocsEntityIconLink = EntityIconLinkBlueprint.make({ /** @alpha */ const techDocsStorageApi = ApiBlueprint.make({ name: 'storage', - params: { - factory: createApiFactory({ + params: define => + define({ api: techdocsStorageApiRef, deps: { configApi: configApiRef, @@ -83,13 +82,12 @@ const techDocsStorageApi = ApiBlueprint.make({ fetchApi, }), }), - }, }); /** @alpha */ const techDocsClientApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: define => + define({ api: techdocsApiRef, deps: { configApi: configApiRef, @@ -103,7 +101,6 @@ const techDocsClientApi = ApiBlueprint.make({ fetchApi, }), }), - }, }); /** @alpha */