refactor(catalog-model): rename API v1beta2 to v1alpha2

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2026-04-21 11:13:27 +02:00
parent dddcc0b382
commit 2cf453f328
10 changed files with 84 additions and 84 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-model': minor
---
Added `backstage.io/v1alpha2` of the `API` kind. It behaves identically to `v1alpha1` / `v1beta1` for the existing string-`definition` shape, and additionally supports a new `spec.type: 'mcp-server'` subtype that carries a structured `spec.remotes` list for representing Model Context Protocol (MCP) servers in the catalog. See RFC [#32062](https://github.com/backstage/backstage/issues/32062). New public exports: `ApiEntityV1alpha2`, `ApiEntityV1alpha2Default`, `McpServerApiEntityV1alpha2`, `McpServerRemote`, `apiEntityV1alpha2Validator`, `mcpServerApiEntityV1alpha2Validator`, and the `isMcpServerApiEntity` type guard.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/catalog-model': minor
---
Added `backstage.io/v1beta2` of the `API` kind. It behaves identically to `v1alpha1` / `v1beta1` for the existing string-`definition` shape, and additionally supports a new `spec.type: 'mcp-server'` subtype that carries a structured `spec.remotes` list for representing Model Context Protocol (MCP) servers in the catalog. See RFC [#32062](https://github.com/backstage/backstage/issues/32062). New public exports: `ApiEntityV1beta2`, `ApiEntityV1beta2Default`, `McpServerApiEntityV1beta2`, `McpServerRemote`, `apiEntityV1beta2Validator`, `mcpServerApiEntityV1beta2Validator`, and the `isMcpServerApiEntity` type guard.
+11 -11
View File
@@ -54,14 +54,14 @@ export { ApiEntityV1alpha1 };
export const apiEntityV1alpha1Validator: KindValidator;
// @public
export type ApiEntityV1beta2 =
| ApiEntityV1beta2Default
| McpServerApiEntityV1beta2;
export type ApiEntityV1alpha2 =
| ApiEntityV1alpha2Default
| McpServerApiEntityV1alpha2;
// @public
export interface ApiEntityV1beta2Default extends Entity {
export interface ApiEntityV1alpha2Default extends Entity {
// (undocumented)
apiVersion: 'backstage.io/v1beta2';
apiVersion: 'backstage.io/v1alpha2';
// (undocumented)
kind: 'API';
// (undocumented)
@@ -75,7 +75,7 @@ export interface ApiEntityV1beta2Default extends Entity {
}
// @public
export const apiEntityV1beta2Validator: KindValidator;
export const apiEntityV1alpha2Validator: KindValidator;
// @public
export class CommonValidatorFunctions {
@@ -299,8 +299,8 @@ export function isLocationEntity(
// @public
export function isMcpServerApiEntity(
entity: ApiEntityV1beta2,
): entity is McpServerApiEntityV1beta2;
entity: ApiEntityV1alpha2,
): entity is McpServerApiEntityV1alpha2;
// @public (undocumented)
export function isResourceEntity(
@@ -362,9 +362,9 @@ export const locationEntityV1alpha1Validator: KindValidator;
export function makeValidator(overrides?: Partial<Validators>): Validators;
// @public
export interface McpServerApiEntityV1beta2 extends Entity {
export interface McpServerApiEntityV1alpha2 extends Entity {
// (undocumented)
apiVersion: 'backstage.io/v1beta2';
apiVersion: 'backstage.io/v1alpha2';
// (undocumented)
kind: 'API';
// (undocumented)
@@ -378,7 +378,7 @@ export interface McpServerApiEntityV1beta2 extends Entity {
}
// @public
export const mcpServerApiEntityV1beta2Validator: KindValidator;
export const mcpServerApiEntityV1alpha2Validator: KindValidator;
// @public
export type McpServerRemote = {
@@ -17,8 +17,8 @@
import { createCatalogModelLayer } from '../model/createCatalogModelLayer';
import type { Entity } from '../entity/Entity';
import jsonSchema from '../schema/kinds/API.v1alpha1.schema.json';
import defaultSchemaV1beta2 from '../schema/kinds/API.v1beta2.schema.json';
import mcpServerSchemaV1beta2 from '../schema/kinds/API.v1beta2.mcp-server.schema.json';
import defaultSchemaV1alpha2 from '../schema/kinds/API.v1alpha2.schema.json';
import mcpServerSchemaV1alpha2 from '../schema/kinds/API.v1alpha2.mcp-server.schema.json';
import { ajvCompiledJsonSchemaValidator } from './util';
/**
@@ -90,17 +90,17 @@ export const apiEntityModel = createCatalogModelLayer({
schema: { jsonSchema },
},
{
name: 'v1beta2',
name: 'v1alpha2',
relationFields: apiRelationFields,
schema: { jsonSchema: defaultSchemaV1beta2 },
schema: { jsonSchema: defaultSchemaV1alpha2 },
},
{
name: 'v1beta2',
name: 'v1alpha2',
specType: 'mcp-server',
description:
'An MCP (Model Context Protocol) server exposed as an API entity.',
relationFields: apiRelationFields,
schema: { jsonSchema: mcpServerSchemaV1beta2 },
schema: { jsonSchema: mcpServerSchemaV1alpha2 },
},
],
});
@@ -15,19 +15,19 @@
*/
import {
ApiEntityV1beta2Default,
McpServerApiEntityV1beta2,
apiEntityV1beta2Validator,
mcpServerApiEntityV1beta2Validator,
ApiEntityV1alpha2Default,
McpServerApiEntityV1alpha2,
apiEntityV1alpha2Validator,
mcpServerApiEntityV1alpha2Validator,
isMcpServerApiEntity,
} from './ApiEntityV1beta2';
} from './ApiEntityV1alpha2';
describe('apiEntityV1beta2Validator (default specType)', () => {
let entity: ApiEntityV1beta2Default;
describe('apiEntityV1alpha2Validator (default specType)', () => {
let entity: ApiEntityV1alpha2Default;
beforeEach(() => {
entity = {
apiVersion: 'backstage.io/v1beta2',
apiVersion: 'backstage.io/v1alpha2',
kind: 'API',
metadata: { name: 'test' },
spec: {
@@ -40,36 +40,36 @@ describe('apiEntityV1beta2Validator (default specType)', () => {
};
});
it('accepts a valid v1beta2 string-definition entity', async () => {
await expect(apiEntityV1beta2Validator.check(entity)).resolves.toBe(true);
it('accepts a valid v1alpha2 string-definition entity', async () => {
await expect(apiEntityV1alpha2Validator.check(entity)).resolves.toBe(true);
});
it('ignores v1alpha1', async () => {
(entity as any).apiVersion = 'backstage.io/v1alpha1';
await expect(apiEntityV1beta2Validator.check(entity)).resolves.toBe(false);
await expect(apiEntityV1alpha2Validator.check(entity)).resolves.toBe(false);
});
it('rejects missing definition', async () => {
delete (entity as any).spec.definition;
await expect(apiEntityV1beta2Validator.check(entity)).rejects.toThrow(
await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow(
/definition/,
);
});
it('rejects missing lifecycle', async () => {
delete (entity as any).spec.lifecycle;
await expect(apiEntityV1beta2Validator.check(entity)).rejects.toThrow(
await expect(apiEntityV1alpha2Validator.check(entity)).rejects.toThrow(
/lifecycle/,
);
});
});
describe('mcpServerApiEntityV1beta2Validator', () => {
let entity: McpServerApiEntityV1beta2;
describe('mcpServerApiEntityV1alpha2Validator', () => {
let entity: McpServerApiEntityV1alpha2;
beforeEach(() => {
entity = {
apiVersion: 'backstage.io/v1beta2',
apiVersion: 'backstage.io/v1alpha2',
kind: 'API',
metadata: { name: 'test-mcp' },
spec: {
@@ -88,50 +88,50 @@ describe('mcpServerApiEntityV1beta2Validator', () => {
it('accepts a valid mcp-server entity', async () => {
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).resolves.toBe(true);
});
it('rejects wrong spec.type value', async () => {
(entity as any).spec.type = 'openapi';
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).rejects.toThrow(/type/);
});
it('rejects missing remotes', async () => {
delete (entity as any).spec.remotes;
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).rejects.toThrow(/remotes/);
});
it('rejects empty remotes array', async () => {
(entity as any).spec.remotes = [];
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).rejects.toThrow(/remotes/);
});
it('rejects remote missing url', async () => {
(entity as any).spec.remotes[0] = { type: 'stdio' };
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).rejects.toThrow(/url/);
});
it('rejects remote missing type', async () => {
(entity as any).spec.remotes[0] = { url: 'http://x' };
await expect(
mcpServerApiEntityV1beta2Validator.check(entity),
mcpServerApiEntityV1alpha2Validator.check(entity),
).rejects.toThrow(/type/);
});
});
describe('isMcpServerApiEntity', () => {
it('returns true for an mcp-server entity', () => {
const entity: McpServerApiEntityV1beta2 = {
apiVersion: 'backstage.io/v1beta2',
const entity: McpServerApiEntityV1alpha2 = {
apiVersion: 'backstage.io/v1alpha2',
kind: 'API',
metadata: { name: 'm' },
spec: {
@@ -145,8 +145,8 @@ describe('isMcpServerApiEntity', () => {
});
it('returns false for a default entity', () => {
const entity: ApiEntityV1beta2Default = {
apiVersion: 'backstage.io/v1beta2',
const entity: ApiEntityV1alpha2Default = {
apiVersion: 'backstage.io/v1alpha2',
kind: 'API',
metadata: { name: 'a' },
spec: {
@@ -15,29 +15,29 @@
*/
import type { Entity } from '../entity/Entity';
import defaultSchema from '../schema/kinds/API.v1beta2.schema.json';
import mcpServerSchema from '../schema/kinds/API.v1beta2.mcp-server.schema.json';
import defaultSchema from '../schema/kinds/API.v1alpha2.schema.json';
import mcpServerSchema from '../schema/kinds/API.v1alpha2.mcp-server.schema.json';
import { ajvCompiledJsonSchemaValidator } from './util';
/**
* Backstage API kind entity, v1beta2. Introduces structured subtypes via
* Backstage API kind entity, v1alpha2. Introduces structured subtypes via
* spec.type, starting with 'mcp-server'. Other values of spec.type continue
* to use the string-definition shape.
*
* @public
*/
export type ApiEntityV1beta2 =
| ApiEntityV1beta2Default
| McpServerApiEntityV1beta2;
export type ApiEntityV1alpha2 =
| ApiEntityV1alpha2Default
| McpServerApiEntityV1alpha2;
/**
* The default (string-definition) shape for v1beta2 API entities. Applies
* The default (string-definition) shape for v1alpha2 API entities. Applies
* when spec.type is anything other than a declared structured subtype.
*
* @public
*/
export interface ApiEntityV1beta2Default extends Entity {
apiVersion: 'backstage.io/v1beta2';
export interface ApiEntityV1alpha2Default extends Entity {
apiVersion: 'backstage.io/v1alpha2';
kind: 'API';
spec: {
type: string;
@@ -50,12 +50,12 @@ export interface ApiEntityV1beta2Default extends Entity {
/**
* An MCP (Model Context Protocol) server represented as an API entity
* (v1beta2, spec.type: 'mcp-server').
* (v1alpha2, spec.type: 'mcp-server').
*
* @public
*/
export interface McpServerApiEntityV1beta2 extends Entity {
apiVersion: 'backstage.io/v1beta2';
export interface McpServerApiEntityV1alpha2 extends Entity {
apiVersion: 'backstage.io/v1alpha2';
kind: 'API';
spec: {
type: 'mcp-server';
@@ -77,28 +77,28 @@ export type McpServerRemote = {
};
/**
* {@link KindValidator} for the default specType of {@link ApiEntityV1beta2}.
* {@link KindValidator} for the default specType of {@link ApiEntityV1alpha2}.
*
* @public
*/
export const apiEntityV1beta2Validator =
export const apiEntityV1alpha2Validator =
ajvCompiledJsonSchemaValidator(defaultSchema);
/**
* {@link KindValidator} for the `mcp-server` specType of {@link ApiEntityV1beta2}.
* {@link KindValidator} for the `mcp-server` specType of {@link ApiEntityV1alpha2}.
*
* @public
*/
export const mcpServerApiEntityV1beta2Validator =
export const mcpServerApiEntityV1alpha2Validator =
ajvCompiledJsonSchemaValidator(mcpServerSchema);
/**
* Type guard: narrows a v1beta2 API entity to the MCP server subtype.
* Type guard: narrows a v1alpha2 API entity to the MCP server subtype.
*
* @public
*/
export function isMcpServerApiEntity(
entity: ApiEntityV1beta2,
): entity is McpServerApiEntityV1beta2 {
entity: ApiEntityV1alpha2,
): entity is McpServerApiEntityV1alpha2 {
return entity.spec.type === 'mcp-server';
}
@@ -17,18 +17,18 @@
import { compileCatalogModel } from '../model/compileCatalogModel';
import { defaultCatalogEntityModel } from '../model/defaultCatalogEntityModel';
describe('apiEntityModel v1beta2 dispatch', () => {
describe('apiEntityModel v1alpha2 dispatch', () => {
const model = compileCatalogModel([defaultCatalogEntityModel]);
it('routes mcp-server and non-mcp-server v1beta2 entities to different schemas', () => {
it('routes mcp-server and non-mcp-server v1alpha2 entities to different schemas', () => {
const mcp = model.getKind({
kind: 'API',
apiVersion: 'backstage.io/v1beta2',
apiVersion: 'backstage.io/v1alpha2',
spec: { type: 'mcp-server' },
});
const openapi = model.getKind({
kind: 'API',
apiVersion: 'backstage.io/v1beta2',
apiVersion: 'backstage.io/v1alpha2',
spec: { type: 'openapi' },
});
+7 -7
View File
@@ -20,16 +20,16 @@ export type {
ApiEntityV1alpha1,
} from './ApiEntityV1alpha1';
export {
apiEntityV1beta2Validator,
apiEntityV1alpha2Validator,
isMcpServerApiEntity,
mcpServerApiEntityV1beta2Validator,
} from './ApiEntityV1beta2';
mcpServerApiEntityV1alpha2Validator,
} from './ApiEntityV1alpha2';
export type {
ApiEntityV1beta2,
ApiEntityV1beta2Default,
McpServerApiEntityV1beta2,
ApiEntityV1alpha2,
ApiEntityV1alpha2Default,
McpServerApiEntityV1alpha2,
McpServerRemote,
} from './ApiEntityV1beta2';
} from './ApiEntityV1alpha2';
export { componentEntityV1alpha1Validator } from './ComponentEntityV1alpha1';
export type {
ComponentEntityV1alpha1 as ComponentEntity,
@@ -1,10 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "ApiV1beta2McpServer",
"$id": "ApiV1alpha2McpServer",
"description": "An MCP (Model Context Protocol) server exposed as an API entity. See RFC backstage/backstage#32062.",
"examples": [
{
"apiVersion": "backstage.io/v1beta2",
"apiVersion": "backstage.io/v1alpha2",
"kind": "API",
"metadata": {
"name": "backstage-mcp-actions",
@@ -33,7 +33,7 @@
"required": ["spec"],
"properties": {
"apiVersion": {
"enum": ["backstage.io/v1beta2"]
"enum": ["backstage.io/v1alpha2"]
},
"kind": {
"enum": ["API"]
@@ -1,10 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "ApiV1beta2",
"$id": "ApiV1alpha2",
"description": "An API describes an interface that can be exposed by a component. The API can be defined in different formats, like OpenAPI, AsyncAPI, GraphQL, gRPC, or other formats.",
"examples": [
{
"apiVersion": "backstage.io/v1beta2",
"apiVersion": "backstage.io/v1alpha2",
"kind": "API",
"metadata": {
"name": "artist-api",
@@ -34,7 +34,7 @@
"required": ["spec"],
"properties": {
"apiVersion": {
"enum": ["backstage.io/v1beta2"]
"enum": ["backstage.io/v1alpha2"]
},
"kind": {
"enum": ["API"]