diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 8a4e2b5c9b..5a011c058e 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -423,7 +423,7 @@ describe('createSpecializedApp', () => { expect(app.apis.get(testApiRef)).toEqual({ value: 'owner' }); }); - it('should ignore plugin ownership metadata from unsupported opaque ApiRefs', () => { + it('should reject unsupported opaque ApiRef versions', () => { const testApiRef = { $$type: '@backstage/ApiRef', version: 'v0', @@ -439,46 +439,39 @@ describe('createSpecializedApp', () => { readonly 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(() => + 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.apis.get(testApiRef)).toEqual({ value: 'other' }); + ).toThrow("Invalid opaque type instance, got version 'v0', expected 'v1'"); }); it('should not infer app ownership from core-prefixed API ids', () => { diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 20c00c8929..0c47dca6f6 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -43,6 +43,7 @@ import { import { ApiFactoryRegistry, ApiResolver } from '@backstage/core-app-api'; import { createExtensionDataContainer, + OpaqueApiRef, OpaqueFrontendPlugin, } from '@internal/frontend'; @@ -51,8 +52,6 @@ import { resolveExtensionDefinition, toInternalExtension, } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { OpaqueApiRef } from '../../../frontend-plugin-api/src/apis/system/ApiRef'; import { extractRouteInfoFromAppNode, @@ -459,13 +458,9 @@ function createApiFactories(options: { // might need to wait for some future update for API factories. function getApiOwnerId(apiRef: { id: string }): string { if (OpaqueApiRef.isType(apiRef)) { - try { - const { pluginId } = OpaqueApiRef.toInternal(apiRef); - if (pluginId) { - return pluginId; - } - } catch { - // Fall back to legacy ID inference for unsupported opaque ApiRef versions. + const { pluginId } = OpaqueApiRef.toInternal(apiRef); + if (pluginId) { + return pluginId; } } diff --git a/packages/frontend-internal/src/apis/OpaqueApiRef.ts b/packages/frontend-internal/src/apis/OpaqueApiRef.ts new file mode 100644 index 0000000000..8a1b058641 --- /dev/null +++ b/packages/frontend-internal/src/apis/OpaqueApiRef.ts @@ -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 & { + readonly $$type: '@backstage/ApiRef'; + }; + versions: { + readonly version: 'v1'; + readonly pluginId?: string; + }; +}>({ + type: '@backstage/ApiRef', + versions: ['v1'], +}); diff --git a/packages/frontend-internal/src/apis/index.ts b/packages/frontend-internal/src/apis/index.ts new file mode 100644 index 0000000000..f445683652 --- /dev/null +++ b/packages/frontend-internal/src/apis/index.ts @@ -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'; diff --git a/packages/frontend-internal/src/index.ts b/packages/frontend-internal/src/index.ts index 38bfdc53f8..447b488fca 100644 --- a/packages/frontend-internal/src/index.ts +++ b/packages/frontend-internal/src/index.ts @@ -15,4 +15,5 @@ */ export * from './routing'; +export * from './apis'; export * from './wiring'; diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts index 2327cb0631..557341d98a 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { OpaqueType } from '@internal/opaque'; +import { OpaqueApiRef } from '@internal/frontend'; import type { ApiRef } from './types'; /** @@ -31,20 +31,6 @@ type ApiRefBuilderConfig = { pluginId?: string; }; -/** @internal */ -export const OpaqueApiRef = OpaqueType.create<{ - public: ApiRef & { - readonly $$type: '@backstage/ApiRef'; - }; - versions: { - readonly version: 'v1'; - readonly pluginId?: string; - }; -}>({ - type: '@backstage/ApiRef', - versions: ['v1'], -}); - function validateId(id: string): void { const valid = id .split('.')