api-ref: move opaque helper to frontend-internal

Share the internal ApiRef opaque helper through frontend-internal and fail fast when ApiRef-shaped values have an unsupported opaque version.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-17 11:05:00 +01:00
parent 90c3a9d1c0
commit cc0693ec40
6 changed files with 87 additions and 64 deletions
@@ -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', () => {
@@ -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;
}
}