Merge pull request #33365 from backstage/rugvip/opaque-api-ref-type

frontend-plugin-api: convert ApiRef to an opaque type
This commit is contained in:
Patrik Oldsberg
2026-03-17 12:15:05 +01:00
committed by GitHub
45 changed files with 725 additions and 199 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Frontend apps now respect an explicit `pluginId` on `ApiRef`s when deciding which plugin owns an API factory.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-plugin-api': patch
---
Updated `createApiRef` to preserve the direct config call without deprecation warnings while staying compatible with the new frontend API ref typing.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/frontend-plugin-api': patch
---
Added a builder form for `createApiRef` in the new frontend system and deprecated the direct `createApiRef({ ... })` call in favor of `createApiRef().with({ ... })`. The builder form now also preserves literal API ref IDs in the resulting `ApiRef` type.
The `createApiRef().with({ ... })` form can also use an explicit `pluginId` to declare API ownership without encoding the plugin ID into the API ref ID, while keeping that metadata internal to runtime handling.
+2 -2
View File
@@ -28,7 +28,6 @@ import { ComponentType } from 'react';
import { ConfigApi } from '@backstage/frontend-plugin-api';
import { configApiRef } from '@backstage/frontend-plugin-api';
import { createApiFactory } from '@backstage/frontend-plugin-api';
import { createApiRef } from '@backstage/frontend-plugin-api';
import { DiscoveryApi } from '@backstage/frontend-plugin-api';
import { discoveryApiRef } from '@backstage/frontend-plugin-api';
import { ErrorApi } from '@backstage/frontend-plugin-api';
@@ -256,7 +255,8 @@ export { configApiRef };
export { createApiFactory };
export { createApiRef };
// @public
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T>;
// @public
export function createComponentExtension<
@@ -19,9 +19,15 @@ import { createApiRef } from './ApiRef';
describe('ApiRef', () => {
it('should be created', () => {
const ref = createApiRef({ id: 'abc' });
expect(ref.$$type).toBe('@backstage/ApiRef');
expect(ref.id).toBe('abc');
expect(String(ref)).toBe('apiRef{abc}');
expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}');
expect(ref.T).toBeNull();
});
it('should not accept pluginId in the core createApiRef config', () => {
// @ts-expect-error pluginId is not supported in core-plugin-api
createApiRef<string>({ id: 'abc', pluginId: 'test' });
});
it('should reject invalid ids', () => {
@@ -14,5 +14,23 @@
* limitations under the License.
*/
export { createApiRef } from '@backstage/frontend-plugin-api';
export type { ApiRefConfig } from '@backstage/frontend-plugin-api';
import {
createApiRef as createFrontendApiRef,
type ApiRef,
type ApiRefConfig,
} from '@backstage/frontend-plugin-api';
const createFrontendApiRefCompat = createFrontendApiRef as <T>(
config: ApiRefConfig,
) => ApiRef<T>;
/**
* Creates a reference to an API.
*
* @public
*/
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> {
return createFrontendApiRefCompat<T>(config);
}
export type { ApiRefConfig };
@@ -16,6 +16,7 @@
import {
AppTreeApi,
type ApiRef,
appTreeApiRef,
coreExtensionData,
createExtension,
@@ -166,10 +167,13 @@ describe('createSpecializedApp', () => {
"factories": Map {
"core.featureflags" => {
"factory": {
"api": ApiRefImpl {
"config": {
"id": "core.featureflags",
},
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.featureflags",
"pluginId": "app",
"toString": [Function],
"version": "v1",
},
"deps": {},
"factory": [Function],
@@ -178,10 +182,13 @@ describe('createSpecializedApp', () => {
},
"core.app-tree" => {
"factory": {
"api": ApiRefImpl {
"config": {
"id": "core.app-tree",
},
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.app-tree",
"pluginId": "app",
"toString": [Function],
"version": "v1",
},
"deps": {},
"factory": [Function],
@@ -190,10 +197,13 @@ describe('createSpecializedApp', () => {
},
"core.config" => {
"factory": {
"api": ApiRefImpl {
"config": {
"id": "core.config",
},
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.config",
"pluginId": "app",
"toString": [Function],
"version": "v1",
},
"deps": {},
"factory": [Function],
@@ -202,10 +212,13 @@ describe('createSpecializedApp', () => {
},
"core.route-resolution" => {
"factory": {
"api": ApiRefImpl {
"config": {
"id": "core.route-resolution",
},
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.route-resolution",
"pluginId": "app",
"toString": [Function],
"version": "v1",
},
"deps": {},
"factory": [Function],
@@ -214,10 +227,13 @@ describe('createSpecializedApp', () => {
},
"core.identity" => {
"factory": {
"api": ApiRefImpl {
"config": {
"id": "core.identity",
},
"api": {
"$$type": "@backstage/ApiRef",
"T": null,
"id": "core.identity",
"pluginId": "app",
"toString": [Function],
"version": "v1",
},
"deps": {},
"factory": [Function],
@@ -359,6 +375,150 @@ describe('createSpecializedApp', () => {
expect(app.apis.get(testApiRef)).toEqual({ value: 'owner' });
});
it('should select the API factory from an explicitly owned plugin on conflict', () => {
const testApiRef = createApiRef<{ value: string }>().with({
id: 'shared.api',
pluginId: 'owner',
});
const app = createSpecializedApp({
features: [
makeAppPlugin(),
createFrontendPlugin({
pluginId: 'other-before',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'other' }),
}),
}),
],
}),
createFrontendPlugin({
pluginId: 'owner',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'owner' }),
}),
}),
],
}),
],
});
expect(app.errors).toEqual([
expect.objectContaining({
code: 'API_FACTORY_CONFLICT',
message: expect.stringContaining("API 'shared.api'"),
}),
]);
expect(app.apis.get(testApiRef)).toEqual({ value: 'owner' });
});
it('should reject unsupported opaque ApiRef versions', () => {
const testApiRef = {
$$type: '@backstage/ApiRef',
version: 'v0',
id: 'shared.api',
pluginId: 'owner',
T: null as unknown as { value: string },
toString() {
return 'apiRef{shared.api}';
},
} as ApiRef<{ value: string }, 'shared.api'> & {
readonly $$type: '@backstage/ApiRef';
readonly version: 'v0';
readonly pluginId: 'owner';
};
expect(() =>
createSpecializedApp({
features: [
makeAppPlugin(),
createFrontendPlugin({
pluginId: 'other-before',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'other' }),
}),
}),
],
}),
createFrontendPlugin({
pluginId: 'owner',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'owner' }),
}),
}),
],
}),
],
}),
).toThrow("Invalid opaque type instance, got version 'v0', expected 'v1'");
});
it('should not infer app ownership from core-prefixed API ids', () => {
const testApiRef = createApiRef<{ value: string }>({ id: 'core.shared' });
const app = createSpecializedApp({
features: [
makeAppPlugin(),
createFrontendPlugin({
pluginId: 'other-before',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'other' }),
}),
}),
],
}),
createFrontendModule({
pluginId: 'app',
extensions: [
ApiBlueprint.make({
params: defineParams =>
defineParams({
api: testApiRef,
deps: {},
factory: () => ({ value: 'app' }),
}),
}),
],
}),
],
});
expect(app.errors).toEqual([
expect.objectContaining({
code: 'API_FACTORY_CONFLICT',
message: expect.stringContaining("API 'core.shared'"),
}),
]);
expect(app.apis.get(testApiRef)).toEqual({ value: 'other' });
});
it('should allow API overrides within the same plugin', () => {
const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' });
@@ -43,6 +43,7 @@ import {
import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api';
import {
createExtensionDataContainer,
OpaqueApiRef,
OpaqueFrontendPlugin,
} from '@internal/frontend';
@@ -401,14 +402,14 @@ function createApiFactories(options: {
const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory);
if (apiFactory) {
const apiRefId = apiFactory.api.id;
const ownerId = getApiOwnerId(apiRefId);
const ownerId = getApiOwnerId(apiFactory.api);
const pluginId = apiNode.spec.plugin.pluginId ?? 'app';
const existingFactory = factoriesById.get(apiRefId);
// This allows modules to override factories provided by the plugin, but
// it rejects API overrides from other plugins. In the event of a
// conflict, the owning plugin is attempted to be inferred from the API
// reference ID.
// conflict, the owning plugin is inferred from the explicit pluginId or
// legacy plugin-prefixed API reference ID.
if (existingFactory && existingFactory.pluginId !== pluginId) {
const shouldReplace =
ownerId === pluginId && existingFactory.pluginId !== ownerId;
@@ -455,14 +456,19 @@ function createApiFactories(options: {
// TODO(Rugvip): It would be good if this was more explicit, but I think that
// might need to wait for some future update for API factories.
function getApiOwnerId(apiRefId: string): string {
function getApiOwnerId(apiRef: { id: string }): string {
if (OpaqueApiRef.isType(apiRef)) {
const { pluginId } = OpaqueApiRef.toInternal(apiRef);
if (pluginId) {
return pluginId;
}
}
const apiRefId = apiRef.id;
const [prefix, ...rest] = apiRefId.split('.');
if (!prefix) {
return apiRefId;
}
if (prefix === 'core') {
return 'app';
}
if (prefix === 'plugin' && rest[0]) {
return rest[0];
}
@@ -0,0 +1,31 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ApiRef } from '@backstage/frontend-plugin-api';
import { OpaqueType } from '@internal/opaque';
export const OpaqueApiRef = OpaqueType.create<{
public: ApiRef<unknown> & {
readonly $$type: '@backstage/ApiRef';
};
versions: {
readonly version: 'v1';
readonly pluginId?: string;
};
}>({
type: '@backstage/ApiRef',
versions: ['v1'],
});
@@ -0,0 +1,17 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './OpaqueApiRef';
+1
View File
@@ -15,4 +15,5 @@
*/
export * from './routing';
export * from './apis';
export * from './wiring';
@@ -24,7 +24,12 @@ export type PluginWrapperApi = {
};
// @public
export const pluginWrapperApiRef: ApiRef<PluginWrapperApi>;
export const pluginWrapperApiRef: ApiRef<
PluginWrapperApi,
'core.plugin-wrapper'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const PluginWrapperBlueprint: ExtensionBlueprint<{
+163 -57
View File
@@ -31,7 +31,9 @@ export type AlertApi = {
};
// @public
export const alertApiRef: ApiRef<AlertApi>;
export const alertApiRef: ApiRef_2<AlertApi, 'core.alert'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type AlertMessage = {
@@ -46,7 +48,9 @@ export type AnalyticsApi = {
};
// @public
export const analyticsApiRef: ApiRef<AnalyticsApi>;
export const analyticsApiRef: ApiRef_2<AnalyticsApi, 'core.analytics'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const AnalyticsContext: (options: {
@@ -187,9 +191,10 @@ export type ApiHolder = {
};
// @public
export type ApiRef<T> = {
id: string;
T: T;
export type ApiRef<T, TId extends string = string> = {
readonly $$type?: '@backstage/ApiRef';
readonly id: TId;
readonly T: T;
};
// @public
@@ -212,7 +217,9 @@ export type AppLanguageApi = {
};
// @public (undocumented)
export const appLanguageApiRef: ApiRef<AppLanguageApi>;
export const appLanguageApiRef: ApiRef_2<AppLanguageApi, 'core.applanguage'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export interface AppNode {
@@ -285,7 +292,9 @@ export type AppThemeApi = {
};
// @public
export const appThemeApiRef: ApiRef<AppThemeApi>;
export const appThemeApiRef: ApiRef_2<AppThemeApi, 'core.apptheme'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export interface AppTree {
@@ -305,12 +314,17 @@ export interface AppTreeApi {
}
// @public
export const appTreeApiRef: ApiRef_2<AppTreeApi>;
export const appTreeApiRef: ApiRef_2<AppTreeApi, 'core.app-tree'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const atlassianAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>;
export const atlassianAuthApiRef: ApiRef_2<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
'core.auth.atlassian'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type AuthProviderInfo = {
@@ -348,20 +362,28 @@ export type BackstageUserIdentity = {
};
// @public
export const bitbucketAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>;
export const bitbucketAuthApiRef: ApiRef_2<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
'core.auth.bitbucket'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const bitbucketServerAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>;
export const bitbucketServerAuthApiRef: ApiRef_2<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
'core.auth.bitbucket-server'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type ConfigApi = Config;
// @public
export const configApiRef: ApiRef<ConfigApi>;
export const configApiRef: ApiRef_2<Config, 'core.config'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export interface ConfigurableExtensionDataRef<
@@ -415,8 +437,22 @@ export function createApiFactory<Api, Impl extends Api>(
instance: Impl,
): ApiFactory<Api, Impl, {}>;
// @public @deprecated
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T>;
export function createApiRef<T>(): {
with<const TId extends string>(
config: ApiRefConfig & {
id: TId;
pluginId?: string;
},
): ApiRef<T, TId> & {
readonly $$type: '@backstage/ApiRef';
};
};
// @public
export function createExtension<
@@ -869,7 +905,9 @@ export interface DialogApiDialog<TResult = void> {
}
// @public
export const dialogApiRef: ApiRef_2<DialogApi>;
export const dialogApiRef: ApiRef_2<DialogApi, 'core.dialog'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type DiscoveryApi = {
@@ -877,7 +915,9 @@ export type DiscoveryApi = {
};
// @public
export const discoveryApiRef: ApiRef<DiscoveryApi>;
export const discoveryApiRef: ApiRef_2<DiscoveryApi, 'core.discovery'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type ErrorApi = {
@@ -901,7 +941,9 @@ export type ErrorApiErrorContext = {
};
// @public
export const errorApiRef: ApiRef<ErrorApi>;
export const errorApiRef: ApiRef_2<ErrorApi, 'core.error'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export const ErrorDisplay: {
@@ -1285,7 +1327,12 @@ export interface FeatureFlagsApi {
}
// @public
export const featureFlagsApiRef: ApiRef<FeatureFlagsApi>;
export const featureFlagsApiRef: ApiRef_2<
FeatureFlagsApi,
'core.featureflags'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type FeatureFlagsSaveOptions = {
@@ -1317,7 +1364,9 @@ export type FetchApi = {
};
// @public
export const fetchApiRef: ApiRef<FetchApi>;
export const fetchApiRef: ApiRef_2<FetchApi, 'core.fetch'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export type FrontendFeature =
@@ -1390,27 +1439,36 @@ export type FrontendPluginInfoOptions = {
};
// @public
export const githubAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>;
export const githubAuthApiRef: ApiRef_2<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
'core.auth.github'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const gitlabAuthApiRef: ApiRef<
export const gitlabAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.gitlab'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const googleAuthApiRef: ApiRef<
export const googleAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.google'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public @deprecated
export type IconComponent = ComponentType<{
@@ -1430,7 +1488,9 @@ export interface IconsApi {
}
// @public
export const iconsApiRef: ApiRef_2<IconsApi>;
export const iconsApiRef: ApiRef_2<IconsApi, 'core.icons'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type IdentityApi = {
@@ -1443,16 +1503,21 @@ export type IdentityApi = {
};
// @public
export const identityApiRef: ApiRef<IdentityApi>;
export const identityApiRef: ApiRef_2<IdentityApi, 'core.identity'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const microsoftAuthApiRef: ApiRef<
export const microsoftAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.microsoft'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public @deprecated
export const NavItemBlueprint: ExtensionBlueprint_2<{
@@ -1515,7 +1580,12 @@ export type OAuthRequestApi = {
};
// @public
export const oauthRequestApiRef: ApiRef<OAuthRequestApi>;
export const oauthRequestApiRef: ApiRef_2<
OAuthRequestApi,
'core.oauthrequest'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type OAuthRequester<TAuthResponse> = (
@@ -1532,22 +1602,28 @@ export type OAuthRequesterOptions<TOAuthResponse> = {
export type OAuthScope = string | string[];
// @public
export const oktaAuthApiRef: ApiRef<
export const oktaAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.okta'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const oneloginAuthApiRef: ApiRef<
export const oneloginAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.onelogin'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type OpenIdConnectApi = {
@@ -1555,9 +1631,12 @@ export type OpenIdConnectApi = {
};
// @public
export const openshiftAuthApiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>;
export const openshiftAuthApiRef: ApiRef_2<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi,
'core.auth.openshift'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export interface OverridableExtensionDefinition<
@@ -1845,7 +1924,12 @@ export type PluginHeaderActionsApi = {
};
// @public
export const pluginHeaderActionsApiRef: ApiRef_2<PluginHeaderActionsApi>;
export const pluginHeaderActionsApiRef: ApiRef_2<
PluginHeaderActionsApi,
'core.plugin-header-actions'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export interface PluginOptions<
@@ -1887,7 +1971,12 @@ export type PluginWrapperApi = {
};
// @public
export const pluginWrapperApiRef: ApiRef_2<PluginWrapperApi>;
export const pluginWrapperApiRef: ApiRef_2<
PluginWrapperApi,
'core.plugin-wrapper'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export const PluginWrapperBlueprint: ExtensionBlueprint_2<{
@@ -1993,7 +2082,12 @@ export interface RouteResolutionApi {
}
// @public
export const routeResolutionApiRef: ApiRef_2<RouteResolutionApi>;
export const routeResolutionApiRef: ApiRef_2<
RouteResolutionApi,
'core.route-resolution'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type SessionApi = {
@@ -2031,7 +2125,9 @@ export interface StorageApi {
}
// @public
export const storageApiRef: ApiRef<StorageApi>;
export const storageApiRef: ApiRef_2<StorageApi, 'core.storage'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public
export type StorageValueSnapshot<TValue extends JsonValue> =
@@ -2121,7 +2217,12 @@ export interface SwappableComponentsApi {
}
// @public
export const swappableComponentsApiRef: ApiRef_2<SwappableComponentsApi>;
export const swappableComponentsApiRef: ApiRef_2<
SwappableComponentsApi,
'core.swappable-components'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export type TranslationApi = {
@@ -2142,7 +2243,9 @@ export type TranslationApi = {
};
// @public (undocumented)
export const translationApiRef: ApiRef<TranslationApi>;
export const translationApiRef: ApiRef_2<TranslationApi, 'core.translation'> & {
readonly $$type: '@backstage/ApiRef';
};
// @public (undocumented)
export type TranslationFunction<
@@ -2332,13 +2435,16 @@ export const useTranslationRef: <TMessages extends { [key in string]: string }>(
};
// @public
export const vmwareCloudAuthApiRef: ApiRef<
export const vmwareCloudAuthApiRef: ApiRef_2<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
>;
SessionApi,
'core.auth.vmware-cloud'
> & {
readonly $$type: '@backstage/ApiRef';
};
// @public @deprecated
export function withApis<T extends {}>(
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createApiRef, ApiRef } from '../system';
import { createApiRef } from '../system';
import { Observable } from '@backstage/types';
/**
@@ -51,6 +51,7 @@ export type AlertApi = {
*
* @public
*/
export const alertApiRef: ApiRef<AlertApi> = createApiRef({
export const alertApiRef = createApiRef<AlertApi>().with({
id: 'core.alert',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { AnalyticsContextValue } from '../../analytics/types';
/**
@@ -151,6 +151,7 @@ export type AnalyticsApi = {
*
* @public
*/
export const analyticsApiRef: ApiRef<AnalyticsApi> = createApiRef({
export const analyticsApiRef = createApiRef<AnalyticsApi>().with({
id: 'core.analytics',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { Observable } from '@backstage/types';
/** @public */
@@ -31,6 +31,7 @@ export type AppLanguageApi = {
/**
* @public
*/
export const appLanguageApiRef: ApiRef<AppLanguageApi> = createApiRef({
export const appLanguageApiRef = createApiRef<AppLanguageApi>().with({
id: 'core.applanguage',
pluginId: 'app',
});
@@ -15,7 +15,7 @@
*/
import { ReactNode } from 'react';
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { Observable } from '@backstage/types';
/**
@@ -82,6 +82,7 @@ export type AppThemeApi = {
*
* @public
*/
export const appThemeApiRef: ApiRef<AppThemeApi> = createApiRef({
export const appThemeApiRef = createApiRef<AppThemeApi>().with({
id: 'core.apptheme',
pluginId: 'app',
});
@@ -117,4 +117,7 @@ export interface AppTreeApi {
*
* @public
*/
export const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });
export const appTreeApiRef = createApiRef<AppTreeApi>().with({
id: 'core.app-tree',
pluginId: 'app',
});
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import type { Config } from '@backstage/config';
/**
@@ -29,6 +29,7 @@ export type ConfigApi = Config;
*
* @public
*/
export const configApiRef: ApiRef<ConfigApi> = createApiRef({
export const configApiRef = createApiRef<ConfigApi>().with({
id: 'core.config',
pluginId: 'app',
});
@@ -173,6 +173,7 @@ export interface DialogApi {
*
* @public
*/
export const dialogApiRef = createApiRef<DialogApi>({
export const dialogApiRef = createApiRef<DialogApi>().with({
id: 'core.dialog',
pluginId: 'app',
});
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
/**
* The discovery API is used to provide a mechanism for plugins to
@@ -50,6 +50,7 @@ export type DiscoveryApi = {
*
* @public
*/
export const discoveryApiRef: ApiRef<DiscoveryApi> = createApiRef({
export const discoveryApiRef = createApiRef<DiscoveryApi>().with({
id: 'core.discovery',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { Observable } from '@backstage/types';
/**
@@ -86,6 +86,7 @@ export type ErrorApi = {
*
* @public
*/
export const errorApiRef: ApiRef<ErrorApi> = createApiRef({
export const errorApiRef = createApiRef<ErrorApi>().with({
id: 'core.error',
pluginId: 'app',
});
@@ -16,7 +16,7 @@
/* We want to maintain the same information as an enum, so we disable the redeclaration warning */
/* eslint-disable @typescript-eslint/no-redeclare */
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
/**
* Feature flag descriptor.
@@ -121,6 +121,7 @@ export interface FeatureFlagsApi {
*
* @public
*/
export const featureFlagsApiRef: ApiRef<FeatureFlagsApi> = createApiRef({
export const featureFlagsApiRef = createApiRef<FeatureFlagsApi>().with({
id: 'core.featureflags',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
/**
* A wrapper for the fetch API, that has additional behaviors such as the
@@ -46,6 +46,7 @@ export type FetchApi = {
*
* @public
*/
export const fetchApiRef: ApiRef<FetchApi> = createApiRef({
export const fetchApiRef = createApiRef<FetchApi>().with({
id: 'core.fetch',
pluginId: 'app',
});
@@ -41,6 +41,7 @@ export interface IconsApi {
*
* @public
*/
export const iconsApiRef = createApiRef<IconsApi>({
export const iconsApiRef = createApiRef<IconsApi>().with({
id: 'core.icons',
pluginId: 'app',
});
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { BackstageUserIdentity, ProfileInfo } from './auth';
/**
@@ -51,6 +51,7 @@ export type IdentityApi = {
*
* @public
*/
export const identityApiRef: ApiRef<IdentityApi> = createApiRef({
export const identityApiRef = createApiRef<IdentityApi>().with({
id: 'core.identity',
pluginId: 'app',
});
@@ -15,7 +15,7 @@
*/
import { Observable } from '@backstage/types';
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { AuthProviderInfo } from './auth';
/**
@@ -126,6 +126,7 @@ export type OAuthRequestApi = {
*
* @public
*/
export const oauthRequestApiRef: ApiRef<OAuthRequestApi> = createApiRef({
export const oauthRequestApiRef = createApiRef<OAuthRequestApi>().with({
id: 'core.oauthrequest',
pluginId: 'app',
});
@@ -40,6 +40,8 @@ export type PluginHeaderActionsApi = {
*
* @public
*/
export const pluginHeaderActionsApiRef = createApiRef<PluginHeaderActionsApi>({
id: 'core.plugin-header-actions',
});
export const pluginHeaderActionsApiRef =
createApiRef<PluginHeaderActionsApi>().with({
id: 'core.plugin-header-actions',
pluginId: 'app',
});
@@ -47,6 +47,7 @@ export type PluginWrapperApi = {
*
* @public
*/
export const pluginWrapperApiRef = createApiRef<PluginWrapperApi>({
export const pluginWrapperApiRef = createApiRef<PluginWrapperApi>().with({
id: 'core.plugin-wrapper',
pluginId: 'app',
});
@@ -65,6 +65,7 @@ export interface RouteResolutionApi {
*
* @public
*/
export const routeResolutionApiRef = createApiRef<RouteResolutionApi>({
export const routeResolutionApiRef = createApiRef<RouteResolutionApi>().with({
id: 'core.route-resolution',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { JsonValue, Observable } from '@backstage/types';
/**
@@ -105,6 +105,7 @@ export interface StorageApi {
*
* @public
*/
export const storageApiRef: ApiRef<StorageApi> = createApiRef({
export const storageApiRef = createApiRef<StorageApi>().with({
id: 'core.storage',
pluginId: 'app',
});
@@ -36,6 +36,8 @@ export interface SwappableComponentsApi {
*
* @public
*/
export const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({
id: 'core.swappable-components',
});
export const swappableComponentsApiRef =
createApiRef<SwappableComponentsApi>().with({
id: 'core.swappable-components',
pluginId: 'app',
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { Expand, ExpandRecursive, Observable } from '@backstage/types';
import { TranslationRef } from '../../translation';
import { JSX } from 'react';
@@ -358,6 +358,7 @@ export type TranslationApi = {
/**
* @public
*/
export const translationApiRef: ApiRef<TranslationApi> = createApiRef({
export const translationApiRef = createApiRef<TranslationApi>().with({
id: 'core.translation',
pluginId: 'app',
});
@@ -16,7 +16,7 @@
/* We want to maintain the same information as an enum, so we disable the redeclaration warning */
/* eslint-disable @typescript-eslint/no-redeclare */
import { ApiRef, createApiRef } from '../system';
import { createApiRef } from '../system';
import { IconComponent, IconElement } from '../../icons/types';
import { Observable } from '@backstage/types';
@@ -28,7 +28,10 @@ import { Observable } from '@backstage/types';
* For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect,
* would be declared as follows:
*
* const googleAuthApiRef = createApiRef<OAuthApi & OpenIDConnectApi>({ ... })
* const googleAuthApiRef = createApiRef<OAuthApi & OpenIDConnectApi>().with({
* id: 'core.auth.google',
* pluginId: 'app',
* })
*/
/**
@@ -333,14 +336,15 @@ export type SessionApi = {
* Note that the ID token payload is only guaranteed to contain the user's numerical Google ID,
* email and expiration information. Do not rely on any other fields, as they might not be present.
*/
export const googleAuthApiRef: ApiRef<
export const googleAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.google',
pluginId: 'app',
});
/**
@@ -352,10 +356,11 @@ export const googleAuthApiRef: ApiRef<
* See {@link https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/}
* for a full list of supported scopes.
*/
export const githubAuthApiRef: ApiRef<
export const githubAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
>().with({
id: 'core.auth.github',
pluginId: 'app',
});
/**
@@ -367,14 +372,15 @@ export const githubAuthApiRef: ApiRef<
* See {@link https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/}
* for a full list of supported scopes.
*/
export const oktaAuthApiRef: ApiRef<
export const oktaAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.okta',
pluginId: 'app',
});
/**
@@ -386,14 +392,15 @@ export const oktaAuthApiRef: ApiRef<
* See {@link https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token}
* for a full list of supported scopes.
*/
export const gitlabAuthApiRef: ApiRef<
export const gitlabAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.gitlab',
pluginId: 'app',
});
/**
@@ -406,14 +413,15 @@ export const gitlabAuthApiRef: ApiRef<
* - {@link https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent}
* - {@link https://docs.microsoft.com/en-us/graph/permissions-reference}
*/
export const microsoftAuthApiRef: ApiRef<
export const microsoftAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.microsoft',
pluginId: 'app',
});
/**
@@ -421,14 +429,15 @@ export const microsoftAuthApiRef: ApiRef<
*
* @public
*/
export const oneloginAuthApiRef: ApiRef<
export const oneloginAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.onelogin',
pluginId: 'app',
});
/**
@@ -440,10 +449,11 @@ export const oneloginAuthApiRef: ApiRef<
* See {@link https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/}
* for a full list of supported scopes.
*/
export const bitbucketAuthApiRef: ApiRef<
export const bitbucketAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
>().with({
id: 'core.auth.bitbucket',
pluginId: 'app',
});
/**
@@ -455,10 +465,11 @@ export const bitbucketAuthApiRef: ApiRef<
* See {@link https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopes}
* for a full list of supported scopes.
*/
export const bitbucketServerAuthApiRef: ApiRef<
export const bitbucketServerAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
>().with({
id: 'core.auth.bitbucket-server',
pluginId: 'app',
});
/**
@@ -470,10 +481,11 @@ export const bitbucketServerAuthApiRef: ApiRef<
* See {@link https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-and-oauth-2-3LO-apps/}
* for a full list of supported scopes.
*/
export const atlassianAuthApiRef: ApiRef<
export const atlassianAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
>().with({
id: 'core.auth.atlassian',
pluginId: 'app',
});
/**
@@ -485,14 +497,15 @@ export const atlassianAuthApiRef: ApiRef<
* For more info about VMware Cloud identity and access management:
* - {@link https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-53D39337-D93A-4B84-BD18-DDF43C21479A.html}
*/
export const vmwareCloudAuthApiRef: ApiRef<
export const vmwareCloudAuthApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionApi
> = createApiRef({
>().with({
id: 'core.auth.vmware-cloud',
pluginId: 'app',
});
/**
@@ -506,8 +519,9 @@ export const vmwareCloudAuthApiRef: ApiRef<
* {@link https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html-single/authentication_and_authorization/index#tokens-scoping-about_configuring-internal-oauth}
* for available scopes.
*/
export const openshiftAuthApiRef: ApiRef<
export const openshiftAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
>().with({
id: 'core.auth.openshift',
pluginId: 'app',
});
@@ -15,13 +15,54 @@
*/
import { createApiRef } from './ApiRef';
import type { ApiRef as ApiRefType } from './types';
describe('ApiRef', () => {
it('should be created', () => {
it('should be created with config', () => {
const ref = createApiRef({ id: 'abc' });
expect(ref.$$type).toBe('@backstage/ApiRef');
expect(ref.id).toBe('abc');
expect(String(ref)).toBe('apiRef{abc}');
expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}');
expect(ref.T).toBeNull();
});
it('should not accept pluginId with deprecated config form', () => {
expect(createApiRef<string>({ id: 'abc' }).id).toBe('abc');
// @ts-expect-error pluginId is only supported through .with(...)
createApiRef<string>({ id: 'abc', pluginId: 'test' });
});
it('should keep the deprecated config form id wide', () => {
const ref = createApiRef<string>({ id: 'abc' });
const wideRef: ApiRefType<string> = ref;
expect(wideRef.id).toBe('abc');
// @ts-expect-error deprecated config form should not infer literal ids
const literalRef: ApiRefType<string, 'abc'> = ref;
expect(literalRef.id).toBe('abc');
});
it('should be created with builder pattern', () => {
const ref = createApiRef<string>().with({ id: 'abc', pluginId: 'test' });
expect(ref.$$type).toBe('@backstage/ApiRef');
expect(ref.id).toBe('abc');
expect(String(ref)).toBe('apiRef{abc}');
expect(ref.T).toBeNull();
expect((ref as { pluginId?: string }).pluginId).toBe('test');
// @ts-expect-error pluginId is internal runtime metadata
expect(ref.pluginId).toBe('test');
});
it('should infer literal ids with builder pattern', () => {
const ref = createApiRef<string>().with({ id: 'abc', pluginId: 'test' });
const literalRef: ApiRefType<string, 'abc'> = ref;
expect(literalRef.id).toBe('abc');
// @ts-expect-error builder pattern should preserve literal ids
const wrongLiteralRef: ApiRefType<string, 'def'> = ref;
expect(wrongLiteralRef.id).toBe('abc');
});
it('should reject invalid ids', () => {
@@ -47,4 +88,10 @@ describe('ApiRef', () => {
);
}
});
it('should reject invalid ids with builder pattern', () => {
expect(() => createApiRef().with({ id: '123' })).toThrow(
`API id must only contain period separated lowercase alphanum tokens with dashes, got '123'`,
);
});
});
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { OpaqueApiRef } from '@internal/frontend';
import type { ApiRef } from './types';
/**
@@ -25,48 +26,110 @@ export type ApiRefConfig = {
id: string;
};
class ApiRefImpl<T> implements ApiRef<T> {
constructor(private readonly config: ApiRefConfig) {
const valid = config.id
.split('.')
.flatMap(part => part.split('-'))
.every(part => part.match(/^[a-z][a-z0-9]*$/));
if (!valid) {
throw new Error(
`API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`,
);
}
}
type ApiRefBuilderConfig<TId extends string> = {
id: TId;
pluginId?: string;
};
get id(): string {
return this.config.id;
}
// Utility for getting type of an api, using `typeof apiRef.T`
get T(): T {
throw new Error(`tried to read ApiRef.T of ${this}`);
}
toString() {
return `apiRef{${this.config.id}}`;
function validateId(id: string): void {
const valid = id
.split('.')
.flatMap(part => part.split('-'))
.every(part => part.match(/^[a-z][a-z0-9]*$/));
if (!valid) {
throw new Error(
`API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`,
);
}
}
function makeApiRef<T, TId extends string>(
config: ApiRefBuilderConfig<TId>,
): ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' } {
return OpaqueApiRef.createInstance('v1', {
id: config.id,
...(config.pluginId ? { pluginId: config.pluginId } : {}),
T: null as unknown as T,
toString() {
return `apiRef{${config.id}}`;
},
}) as ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' };
}
/**
* Creates a reference to an API. The provided `id` is a stable identifier for
* the API implementation.
* Creates a reference to an API.
*
* @remarks
*
* The frontend system infers the owning plugin for an API from the `id`. The
* The `id` is a stable identifier for the API implementation. The frontend
* system infers the owning plugin for an API from the `id`. When using the
* builder form, you can instead provide a `pluginId` explicitly. The
* recommended pattern is `plugin.<plugin-id>.*` (for example,
* `plugin.catalog.entity-presentation`). This ensures that other plugins can't
* mistakenly override your API implementation.
*
* @param config - The descriptor of the API to reference.
* @returns An API reference.
* The recommended way to create an API reference is:
*
* ```ts
* const myApiRef = createApiRef<MyApi>().with({
* id: 'my-api',
* pluginId: 'my-plugin',
* });
* ```
*
* The legacy way to create an API reference is:
*
* ```ts
* const myApiRef = createApiRef<MyApi>({ id: 'plugin.my.api' });
* ```
*
* @public
*/
export function createApiRef<T>(config: ApiRefConfig): ApiRef<T> {
return new ApiRefImpl<T>(config);
/**
* Creates a reference to an API.
*
* @deprecated Use `createApiRef<T>().with(...)` instead.
* @public
*/
export function createApiRef<T>(
config: ApiRefConfig,
): ApiRef<T> & { readonly $$type: '@backstage/ApiRef' };
/**
* Creates a reference to an API.
*
* @remarks
*
* Returns a builder with a `.with()` method for providing the API reference
* configuration.
*
* @public
*/
export function createApiRef<T>(): {
with<const TId extends string>(
config: ApiRefConfig & { id: TId; pluginId?: string },
): ApiRef<T, TId> & {
readonly $$type: '@backstage/ApiRef';
};
};
export function createApiRef<T>(config?: ApiRefConfig):
| (ApiRef<T> & { readonly $$type: '@backstage/ApiRef' })
| {
with<const TId extends string>(
config: ApiRefConfig & { id: TId; pluginId?: string },
): ApiRef<T, TId> & {
readonly $$type: '@backstage/ApiRef';
};
} {
if (config) {
validateId(config.id);
return makeApiRef<T, string>(config);
}
return {
with<const TId extends string>(
withConfig: ApiRefConfig & { id: TId; pluginId?: string },
): ApiRef<T, TId> & { readonly $$type: '@backstage/ApiRef' } {
validateId(withConfig.id);
return makeApiRef<T, TId>(withConfig);
},
};
}
@@ -19,9 +19,10 @@
*
* @public
*/
export type ApiRef<T> = {
id: string;
T: T;
export type ApiRef<T, TId extends string = string> = {
readonly $$type?: '@backstage/ApiRef';
readonly id: TId;
readonly T: T;
};
/**
@@ -38,7 +38,9 @@ describe('useApiHolder', () => {
const renderedHook = renderHook(() => useApiHolder());
const holder = renderedHook.result.current;
expect(holder.get(createApiRef<string>({ id: 'x' }))).toBeUndefined();
expect(
holder.get(createApiRef<string>().with({ id: 'x' })),
).toBeUndefined();
});
});
@@ -53,7 +55,7 @@ describe('useApi', () => {
const get = jest.fn(() => 'my-api-impl');
context.set({ 1: { get } });
const apiRef = createApiRef<string>({ id: 'x' });
const apiRef = createApiRef<string>().with({ id: 'x' });
const renderedHook = renderHook(() => useApi(apiRef));
const value = renderedHook.result.current;
@@ -20,7 +20,7 @@ import { createApiRef } from '../apis/system';
describe('ApiBlueprint', () => {
it('should create an extension with sensible defaults', () => {
const api = createApiRef<{ foo: string }>({ id: 'test' });
const api = createApiRef<{ foo: string }>().with({ id: 'test' });
const extension = ApiBlueprint.make({
params: defineParams =>
@@ -57,8 +57,8 @@ describe('ApiBlueprint', () => {
});
it('should properly type the API factory', () => {
const fooApi = createApiRef<{ foo: string }>({ id: 'foo' });
const barApi = createApiRef<{ bar: string }>({ id: 'bar' });
const fooApi = createApiRef<{ foo: string }>().with({ id: 'foo' });
const barApi = createApiRef<{ bar: string }>().with({ id: 'bar' });
expect('test').not.toBe('failing without assertions');
@@ -152,7 +152,7 @@ describe('ApiBlueprint', () => {
});
it('should create an extension with custom factory', () => {
const api = createApiRef<{ foo: string }>({ id: 'test' });
const api = createApiRef<{ foo: string }>().with({ id: 'test' });
const factory = jest.fn(() => ({ foo: 'bar' }));
const extension = ApiBlueprint.makeWithOverrides({
+2 -2
View File
@@ -86,7 +86,7 @@ export const IconBundleBlueprint: ExtensionBlueprint<{
};
output: ExtensionDataRef<
{
[x: string]: IconComponent | IconElement;
[x: string]: IconElement | IconComponent;
},
'core.icons',
{}
@@ -97,7 +97,7 @@ export const IconBundleBlueprint: ExtensionBlueprint<{
dataRefs: {
icons: ConfigurableExtensionDataRef<
{
[x: string]: IconComponent | IconElement;
[x: string]: IconElement | IconComponent;
},
'core.icons',
{}
+1 -1
View File
@@ -478,7 +478,7 @@ const appPlugin: OverridableFrontendPlugin<
icons: ExtensionInput<
ConfigurableExtensionDataRef<
{
[x: string]: IconComponent | IconElement;
[x: string]: IconElement | IconComponent;
},
'core.icons',
{}
+3 -1
View File
@@ -200,7 +200,9 @@ export type FormFieldExtensionData<
};
// @alpha (undocumented)
export const formFieldsApiRef: ApiRef<ScaffolderFormFieldsApi>;
export const formFieldsApiRef: ApiRef<ScaffolderFormFieldsApi> & {
readonly $$type: '@backstage/ApiRef';
};
// @alpha (undocumented)
export type FormValidation = {
+3 -1
View File
@@ -208,7 +208,9 @@ export type ReviewStepProps = {
export type ScaffolderApi = ScaffolderApi_2;
// @public (undocumented)
export const scaffolderApiRef: ApiRef<ScaffolderApi_2>;
export const scaffolderApiRef: ApiRef<ScaffolderApi_2> & {
readonly $$type: '@backstage/ApiRef';
};
// @public @deprecated (undocumented)
export type ScaffolderDryRunOptions = ScaffolderDryRunOptions_2;
+3 -1
View File
@@ -479,7 +479,9 @@ export const formDecoratorsApi: OverridableExtensionDefinition<{
}>;
// @alpha (undocumented)
export const formDecoratorsApiRef: ApiRef<ScaffolderFormDecoratorsApi>;
export const formDecoratorsApiRef: ApiRef<ScaffolderFormDecoratorsApi> & {
readonly $$type: '@backstage/ApiRef';
};
export { formFieldsApiRef };
+3 -1
View File
@@ -528,7 +528,9 @@ export type RouterProps = {
export type ScaffolderApi = ScaffolderApi_2;
// @public @deprecated (undocumented)
export const scaffolderApiRef: ApiRef<ScaffolderApi_2>;
export const scaffolderApiRef: ApiRef<ScaffolderApi_2> & {
readonly $$type: '@backstage/ApiRef';
};
// @public @deprecated
export class ScaffolderClient extends ScaffolderClient_2 {}