Merge pull request #34261 from backstage/session/scheming-finch-ks70
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-ai-model': minor
|
||||
'@backstage/catalog-model': minor
|
||||
---
|
||||
|
||||
Introduced the `AiResource` catalog entity kind. Entity types, validators, type guards, and the model layer are exported from `@backstage/catalog-model/alpha`. Install `@backstage/plugin-catalog-backend-module-ai-model` in your backend to register the kind with the catalog.
|
||||
@@ -280,6 +280,7 @@ catalog:
|
||||
pullRequestBranchName: backstage-integration
|
||||
rules:
|
||||
- allow:
|
||||
- AiResource
|
||||
- Component
|
||||
- API
|
||||
- Resource
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-ai-model": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-logs": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^",
|
||||
|
||||
@@ -46,6 +46,7 @@ backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
|
||||
backend.add(import('@backstage/plugin-auth-backend-module-openshift-provider'));
|
||||
backend.add(import('@backstage/plugin-app-backend'));
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed'));
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-ai-model'));
|
||||
backend.add(
|
||||
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: AiResource
|
||||
metadata:
|
||||
name: frontend-design
|
||||
description: Skill for creating production-grade frontend interfaces using the internal design system
|
||||
spec:
|
||||
type: skill
|
||||
lifecycle: production
|
||||
owner: team-a
|
||||
system: artist-engagement-portal
|
||||
disciplines:
|
||||
- web
|
||||
categories:
|
||||
- framework
|
||||
- design-system
|
||||
agents:
|
||||
- claude-code
|
||||
- copilot
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: AiResource
|
||||
metadata:
|
||||
name: use-internal-apis
|
||||
description: Agents must use internal API clients instead of raw HTTP calls
|
||||
spec:
|
||||
type: rule
|
||||
lifecycle: production
|
||||
owner: team-a
|
||||
system: artist-engagement-portal
|
||||
disciplines:
|
||||
- web
|
||||
- backend
|
||||
category: architecture
|
||||
rationale: Ensures consistent error handling, authentication, and observability across all service calls
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Location
|
||||
metadata:
|
||||
name: example-ai-resources
|
||||
description: A collection of all Backstage example AI resources
|
||||
spec:
|
||||
targets:
|
||||
- ./ai-resources/frontend-design-skill.yaml
|
||||
- ./ai-resources/use-internal-apis-rule.yaml
|
||||
@@ -5,6 +5,7 @@ metadata:
|
||||
description: A collection of all Backstage example entities, except users, groups, and templates
|
||||
spec:
|
||||
targets:
|
||||
- ./all-ai-resources.yaml
|
||||
- ./all-apis.yaml
|
||||
- ./all-components.yaml
|
||||
- ./all-domains.yaml
|
||||
|
||||
@@ -3,13 +3,40 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import type { Entity } from '@backstage/catalog-model';
|
||||
import type { Entity as Entity_2 } from '@backstage/catalog-model';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { SerializedError } from '@backstage/errors';
|
||||
|
||||
// @alpha
|
||||
export interface AlphaEntity extends Entity {
|
||||
export const aiResourceEntityModel: CatalogModelLayer;
|
||||
|
||||
// @alpha
|
||||
export type AiResourceEntityV1alpha1 =
|
||||
| AiResourceEntityV1alpha1Default
|
||||
| SkillAiResourceEntityV1alpha1
|
||||
| RuleAiResourceEntityV1alpha1;
|
||||
|
||||
// @alpha
|
||||
export interface AiResourceEntityV1alpha1Default extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: 'backstage.io/v1alpha1';
|
||||
// (undocumented)
|
||||
kind: 'AiResource';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const aiResourceEntityV1alpha1Validator: KindValidator;
|
||||
|
||||
// @alpha
|
||||
export interface AlphaEntity extends Entity_2 {
|
||||
status?: EntityStatus;
|
||||
}
|
||||
|
||||
@@ -400,6 +427,43 @@ export function createCatalogModelLayerBuilder(options: {
|
||||
// @alpha
|
||||
export const defaultCatalogEntityModel: CatalogModelLayer;
|
||||
|
||||
// @public
|
||||
export type Entity = {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata: EntityMeta;
|
||||
spec?: JsonObject;
|
||||
relations?: EntityRelation[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export type EntityLink = {
|
||||
url: string;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type EntityMeta = JsonObject & {
|
||||
uid?: string;
|
||||
etag?: string;
|
||||
name: string;
|
||||
namespace?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
labels?: Record<string, string>;
|
||||
annotations?: Record<string, string>;
|
||||
tags?: string[];
|
||||
links?: EntityLink[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export type EntityRelation = {
|
||||
type: string;
|
||||
targetRef: string;
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export type EntityStatus = {
|
||||
items?: EntityStatusItem[];
|
||||
@@ -416,5 +480,62 @@ export type EntityStatusItem = {
|
||||
// @alpha
|
||||
export type EntityStatusLevel = 'info' | 'warning' | 'error';
|
||||
|
||||
// @alpha
|
||||
export const isAiResourceEntity: (
|
||||
entity: Entity,
|
||||
) => entity is AiResourceEntityV1alpha1;
|
||||
|
||||
// @alpha
|
||||
export const isRuleAiResourceEntity: (
|
||||
entity: Entity,
|
||||
) => entity is RuleAiResourceEntityV1alpha1;
|
||||
|
||||
// @alpha
|
||||
export const isSkillAiResourceEntity: (
|
||||
entity: Entity,
|
||||
) => entity is SkillAiResourceEntityV1alpha1;
|
||||
|
||||
// @public
|
||||
export type KindValidator = {
|
||||
check(entity: Entity): Promise<boolean>;
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export interface RuleAiResourceEntityV1alpha1
|
||||
extends AiResourceEntityV1alpha1Default {
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: 'rule';
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
disciplines?: string[];
|
||||
category: string;
|
||||
rationale: string;
|
||||
};
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const ruleAiResourceEntityV1alpha1Validator: KindValidator;
|
||||
|
||||
// @alpha
|
||||
export interface SkillAiResourceEntityV1alpha1
|
||||
extends AiResourceEntityV1alpha1Default {
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: 'skill';
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
disciplines?: string[];
|
||||
categories?: string[];
|
||||
agents?: string[];
|
||||
dependsOn?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const skillAiResourceEntityV1alpha1Validator: KindValidator;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -14,11 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type {
|
||||
Entity,
|
||||
EntityLink,
|
||||
EntityMeta,
|
||||
EntityRelation,
|
||||
} from './entity/Entity';
|
||||
export type { KindValidator } from './kinds/types';
|
||||
export type { AlphaEntity } from './entity/AlphaEntity';
|
||||
export type {
|
||||
EntityStatus,
|
||||
EntityStatusItem,
|
||||
EntityStatusLevel,
|
||||
} from './entity/EntityStatus';
|
||||
export type {
|
||||
AiResourceEntityV1alpha1,
|
||||
AiResourceEntityV1alpha1Default,
|
||||
SkillAiResourceEntityV1alpha1,
|
||||
RuleAiResourceEntityV1alpha1,
|
||||
} from './kinds/AiResourceEntityV1alpha1';
|
||||
export {
|
||||
aiResourceEntityV1alpha1Validator,
|
||||
skillAiResourceEntityV1alpha1Validator,
|
||||
ruleAiResourceEntityV1alpha1Validator,
|
||||
isAiResourceEntity,
|
||||
isSkillAiResourceEntity,
|
||||
isRuleAiResourceEntity,
|
||||
aiResourceEntityModel,
|
||||
} from './kinds/AiResourceEntityV1alpha1';
|
||||
export * from './model';
|
||||
export { defaultCatalogEntityModel } from './model/defaultCatalogEntityModel';
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright 2026 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 { Entity } from '../entity/Entity';
|
||||
import {
|
||||
type AiResourceEntityV1alpha1Default,
|
||||
type SkillAiResourceEntityV1alpha1,
|
||||
type RuleAiResourceEntityV1alpha1,
|
||||
aiResourceEntityV1alpha1Validator as defaultValidator,
|
||||
skillAiResourceEntityV1alpha1Validator as skillValidator,
|
||||
ruleAiResourceEntityV1alpha1Validator as ruleValidator,
|
||||
isAiResourceEntity,
|
||||
isSkillAiResourceEntity,
|
||||
isRuleAiResourceEntity,
|
||||
} from './AiResourceEntityV1alpha1';
|
||||
|
||||
describe('AiResourceV1alpha1 default validator', () => {
|
||||
let entity: AiResourceEntityV1alpha1Default;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: {
|
||||
name: 'internal-design-system',
|
||||
},
|
||||
spec: {
|
||||
type: 'rule',
|
||||
lifecycle: 'production',
|
||||
owner: 'frontend-platform',
|
||||
system: 'ai-tooling',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accepts valid data', async () => {
|
||||
await expect(defaultValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('ignores unknown apiVersion', async () => {
|
||||
(entity as any).apiVersion = 'backstage.io/v1beta0';
|
||||
await expect(defaultValidator.check(entity)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('ignores unknown kind', async () => {
|
||||
(entity as any).kind = 'Wizard';
|
||||
await expect(defaultValidator.check(entity)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('rejects missing type', async () => {
|
||||
delete (entity as any).spec.type;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/type/);
|
||||
});
|
||||
|
||||
it('rejects wrong type', async () => {
|
||||
(entity as any).spec.type = 7;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/type/);
|
||||
});
|
||||
|
||||
it('rejects empty type', async () => {
|
||||
(entity as any).spec.type = '';
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/type/);
|
||||
});
|
||||
|
||||
it('rejects missing lifecycle', async () => {
|
||||
delete (entity as any).spec.lifecycle;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/lifecycle/);
|
||||
});
|
||||
|
||||
it('rejects wrong lifecycle', async () => {
|
||||
(entity as any).spec.lifecycle = 7;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/lifecycle/);
|
||||
});
|
||||
|
||||
it('rejects empty lifecycle', async () => {
|
||||
(entity as any).spec.lifecycle = '';
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/lifecycle/);
|
||||
});
|
||||
|
||||
it('rejects missing owner', async () => {
|
||||
delete (entity as any).spec.owner;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
|
||||
it('rejects wrong owner', async () => {
|
||||
(entity as any).spec.owner = 7;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
|
||||
it('rejects empty owner', async () => {
|
||||
(entity as any).spec.owner = '';
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
|
||||
it('accepts missing system', async () => {
|
||||
delete (entity as any).spec.system;
|
||||
await expect(defaultValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects wrong system', async () => {
|
||||
(entity as any).spec.system = 7;
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/system/);
|
||||
});
|
||||
|
||||
it('rejects empty system', async () => {
|
||||
(entity as any).spec.system = '';
|
||||
await expect(defaultValidator.check(entity)).rejects.toThrow(/system/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AiResourceV1alpha1 skill validator', () => {
|
||||
let entity: SkillAiResourceEntityV1alpha1;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: {
|
||||
name: 'frontend-design',
|
||||
},
|
||||
spec: {
|
||||
type: 'skill',
|
||||
lifecycle: 'production',
|
||||
owner: 'ai-platform-team',
|
||||
system: 'ai-tooling',
|
||||
disciplines: ['web', 'backend'],
|
||||
categories: ['framework'],
|
||||
agents: ['claude-code'],
|
||||
dependsOn: ['airesource:default/base-coding-standards'],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accepts valid skill data with all fields', async () => {
|
||||
await expect(skillValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('accepts skill with only required fields', async () => {
|
||||
entity.spec = {
|
||||
type: 'skill',
|
||||
lifecycle: 'experimental',
|
||||
owner: 'team-a',
|
||||
};
|
||||
await expect(skillValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects non-skill type', async () => {
|
||||
(entity as any).spec.type = 'rule';
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/type/);
|
||||
});
|
||||
|
||||
it('rejects missing lifecycle', async () => {
|
||||
delete (entity as any).spec.lifecycle;
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/lifecycle/);
|
||||
});
|
||||
|
||||
it('rejects missing owner', async () => {
|
||||
delete (entity as any).spec.owner;
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
|
||||
it('accepts missing optional fields', async () => {
|
||||
delete (entity as any).spec.system;
|
||||
delete (entity as any).spec.disciplines;
|
||||
delete (entity as any).spec.categories;
|
||||
delete (entity as any).spec.agents;
|
||||
delete (entity as any).spec.dependsOn;
|
||||
await expect(skillValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects disciplines with empty strings', async () => {
|
||||
(entity as any).spec.disciplines = [''];
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/disciplines/);
|
||||
});
|
||||
|
||||
it('rejects categories with wrong type', async () => {
|
||||
(entity as any).spec.categories = 'not-an-array';
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/categories/);
|
||||
});
|
||||
|
||||
it('rejects agents with wrong item type', async () => {
|
||||
(entity as any).spec.agents = [42];
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/agents/);
|
||||
});
|
||||
|
||||
it('rejects dependsOn with empty strings', async () => {
|
||||
(entity as any).spec.dependsOn = [''];
|
||||
await expect(skillValidator.check(entity)).rejects.toThrow(/dependsOn/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AiResourceV1alpha1 rule validator', () => {
|
||||
let entity: RuleAiResourceEntityV1alpha1;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: {
|
||||
name: 'use-internal-apis',
|
||||
},
|
||||
spec: {
|
||||
type: 'rule',
|
||||
lifecycle: 'production',
|
||||
owner: 'frontend-platform',
|
||||
disciplines: ['web', 'backend'],
|
||||
category: 'architecture',
|
||||
rationale: 'Ensures consistent error handling across all service calls',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accepts valid rule data with all fields', async () => {
|
||||
await expect(ruleValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('accepts rule with only required fields', async () => {
|
||||
entity.spec = {
|
||||
type: 'rule',
|
||||
lifecycle: 'production',
|
||||
owner: 'team-a',
|
||||
category: 'security',
|
||||
rationale: 'Prevents credential leaks',
|
||||
};
|
||||
await expect(ruleValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects non-rule type', async () => {
|
||||
(entity as any).spec.type = 'skill';
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/type/);
|
||||
});
|
||||
|
||||
it('rejects missing category', async () => {
|
||||
delete (entity as any).spec.category;
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/category/);
|
||||
});
|
||||
|
||||
it('rejects empty category', async () => {
|
||||
(entity as any).spec.category = '';
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/category/);
|
||||
});
|
||||
|
||||
it('rejects missing rationale', async () => {
|
||||
delete (entity as any).spec.rationale;
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/rationale/);
|
||||
});
|
||||
|
||||
it('rejects empty rationale', async () => {
|
||||
(entity as any).spec.rationale = '';
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/rationale/);
|
||||
});
|
||||
|
||||
it('accepts missing optional fields', async () => {
|
||||
delete (entity as any).spec.system;
|
||||
delete (entity as any).spec.disciplines;
|
||||
await expect(ruleValidator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects disciplines with empty strings', async () => {
|
||||
(entity as any).spec.disciplines = [''];
|
||||
await expect(ruleValidator.check(entity)).rejects.toThrow(/disciplines/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAiResourceEntity', () => {
|
||||
it('returns true when apiVersion and kind match', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
};
|
||||
expect(isAiResourceEntity(entity)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a different kind', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'test' },
|
||||
};
|
||||
expect(isAiResourceEntity(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for a different apiVersion', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
};
|
||||
expect(isAiResourceEntity(entity)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSkillAiResourceEntity', () => {
|
||||
it('returns true for a skill AiResource', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
spec: { type: 'skill' },
|
||||
};
|
||||
expect(isSkillAiResourceEntity(entity)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a non-skill AiResource', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
spec: { type: 'rule' },
|
||||
};
|
||||
expect(isSkillAiResourceEntity(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for a different kind', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'test' },
|
||||
spec: { type: 'skill' },
|
||||
};
|
||||
expect(isSkillAiResourceEntity(entity)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRuleAiResourceEntity', () => {
|
||||
it('returns true for a rule AiResource', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
spec: { type: 'rule' },
|
||||
};
|
||||
expect(isRuleAiResourceEntity(entity)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a non-rule AiResource', () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'AiResource',
|
||||
metadata: { name: 'test' },
|
||||
spec: { type: 'skill' },
|
||||
};
|
||||
expect(isRuleAiResourceEntity(entity)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2026 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 { createCatalogModelLayer } from '../model/createCatalogModelLayer';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { entityKindSchemaValidator } from '../validation';
|
||||
import type { KindValidator } from './types';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import defaultJsonSchema from '../schema/kinds/AiResource.v1alpha1.schema.json';
|
||||
import skillJsonSchema from '../schema/kinds/AiResource.v1alpha1.skill.schema.json';
|
||||
import ruleJsonSchema from '../schema/kinds/AiResource.v1alpha1.rule.schema.json';
|
||||
|
||||
/**
|
||||
* Default AiResource entity for types that don't have a structured spec.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* The actual content of skills and rules is not stored in the entity spec.
|
||||
* Instead, the source file is referenced via the standard
|
||||
* `backstage.io/source-location` annotation, consistent with how other
|
||||
* Backstage entities reference their source files.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface AiResourceEntityV1alpha1Default extends Entity {
|
||||
apiVersion: 'backstage.io/v1alpha1';
|
||||
kind: 'AiResource';
|
||||
spec: {
|
||||
type: string;
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AiResource entity with spec.type 'skill'. Represents reusable contextual
|
||||
* knowledge consumed by AI coding tools.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface SkillAiResourceEntityV1alpha1
|
||||
extends AiResourceEntityV1alpha1Default {
|
||||
spec: {
|
||||
type: 'skill';
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
disciplines?: string[];
|
||||
categories?: string[];
|
||||
agents?: string[];
|
||||
dependsOn?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AiResource entity with spec.type 'rule'. Represents a governance rule
|
||||
* or constraint for AI coding tools.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface RuleAiResourceEntityV1alpha1
|
||||
extends AiResourceEntityV1alpha1Default {
|
||||
spec: {
|
||||
type: 'rule';
|
||||
lifecycle: string;
|
||||
owner: string;
|
||||
system?: string;
|
||||
disciplines?: string[];
|
||||
category: string;
|
||||
rationale: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Backstage catalog AiResource kind Entity. Represents contextual information
|
||||
* consumed by AI coding tools, such as skills and rules.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type AiResourceEntityV1alpha1 =
|
||||
| AiResourceEntityV1alpha1Default
|
||||
| SkillAiResourceEntityV1alpha1
|
||||
| RuleAiResourceEntityV1alpha1;
|
||||
|
||||
const defaultValidator = entityKindSchemaValidator(defaultJsonSchema);
|
||||
|
||||
/**
|
||||
* Entity data validator for the default {@link AiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const aiResourceEntityV1alpha1Validator: KindValidator = {
|
||||
async check(data: Entity) {
|
||||
return defaultValidator(data) === data;
|
||||
},
|
||||
};
|
||||
|
||||
const skillValidator = entityKindSchemaValidator(skillJsonSchema);
|
||||
|
||||
/**
|
||||
* Entity data validator for {@link SkillAiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const skillAiResourceEntityV1alpha1Validator: KindValidator = {
|
||||
async check(data: Entity) {
|
||||
return skillValidator(data) === data;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard for {@link AiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const isAiResourceEntity = (
|
||||
entity: Entity,
|
||||
): entity is AiResourceEntityV1alpha1 =>
|
||||
entity.apiVersion === 'backstage.io/v1alpha1' && entity.kind === 'AiResource';
|
||||
|
||||
/**
|
||||
* Type guard for {@link SkillAiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const isSkillAiResourceEntity = (
|
||||
entity: Entity,
|
||||
): entity is SkillAiResourceEntityV1alpha1 =>
|
||||
isAiResourceEntity(entity) && entity.spec?.type === 'skill';
|
||||
|
||||
/**
|
||||
* Type guard for {@link RuleAiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const isRuleAiResourceEntity = (
|
||||
entity: Entity,
|
||||
): entity is RuleAiResourceEntityV1alpha1 =>
|
||||
isAiResourceEntity(entity) && entity.spec?.type === 'rule';
|
||||
|
||||
const ruleValidator = entityKindSchemaValidator(ruleJsonSchema);
|
||||
|
||||
/**
|
||||
* Entity data validator for {@link RuleAiResourceEntityV1alpha1}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const ruleAiResourceEntityV1alpha1Validator: KindValidator = {
|
||||
async check(data: Entity) {
|
||||
return ruleValidator(data) === data;
|
||||
},
|
||||
};
|
||||
|
||||
const baseRelationFields = [
|
||||
{
|
||||
selector: { path: 'spec.owner' },
|
||||
relation: 'ownedBy',
|
||||
defaultKind: 'Group',
|
||||
defaultNamespace: 'inherit' as const,
|
||||
allowedKinds: ['Group', 'User'],
|
||||
},
|
||||
{
|
||||
selector: { path: 'spec.system' },
|
||||
relation: 'partOf',
|
||||
defaultKind: 'System',
|
||||
defaultNamespace: 'inherit' as const,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Extends the catalog model with the AiResource kind.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const aiResourceEntityModel = createCatalogModelLayer({
|
||||
layerId: 'catalog.backstage.io/kind-ai-resource',
|
||||
builder: model => {
|
||||
model.addKind({
|
||||
group: 'backstage.io',
|
||||
names: {
|
||||
kind: 'AiResource',
|
||||
singular: 'airesource',
|
||||
plural: 'airesources',
|
||||
},
|
||||
description:
|
||||
'An AI resource represents contextual information consumed by AI coding tools, such as skills and rules.',
|
||||
versions: [
|
||||
{
|
||||
name: 'v1alpha1',
|
||||
relationFields: baseRelationFields,
|
||||
schema: {
|
||||
jsonSchema: defaultJsonSchema as JsonObject,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'v1alpha1',
|
||||
specType: 'skill',
|
||||
relationFields: [
|
||||
...baseRelationFields,
|
||||
{
|
||||
selector: { path: 'spec.dependsOn' },
|
||||
relation: 'dependsOn',
|
||||
defaultKind: 'AiResource',
|
||||
defaultNamespace: 'inherit' as const,
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
jsonSchema: skillJsonSchema as JsonObject,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'v1alpha1',
|
||||
specType: 'rule',
|
||||
relationFields: baseRelationFields,
|
||||
schema: {
|
||||
jsonSchema: ruleJsonSchema as JsonObject,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "AiResourceV1alpha1Rule",
|
||||
"description": "An AI resource of type 'rule', representing a governance rule or constraint for AI coding tools.",
|
||||
"examples": [
|
||||
{
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "AiResource",
|
||||
"metadata": {
|
||||
"name": "use-internal-apis",
|
||||
"description": "Agents must use internal API clients instead of raw HTTP calls"
|
||||
},
|
||||
"spec": {
|
||||
"type": "rule",
|
||||
"lifecycle": "production",
|
||||
"owner": "frontend-platform",
|
||||
"disciplines": ["web", "backend"],
|
||||
"category": "architecture",
|
||||
"rationale": "Ensures consistent error handling, authentication, and observability across all service calls"
|
||||
}
|
||||
}
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "Entity"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["spec"],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"enum": ["backstage.io/v1alpha1"]
|
||||
},
|
||||
"kind": {
|
||||
"enum": ["AiResource"]
|
||||
},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"required": ["type", "lifecycle", "owner", "category", "rationale"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["rule"],
|
||||
"description": "The type of AI resource."
|
||||
},
|
||||
"lifecycle": {
|
||||
"type": "string",
|
||||
"description": "The lifecycle state of the AI resource.",
|
||||
"examples": ["experimental", "production", "deprecated"],
|
||||
"minLength": 1
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the owner of the AI resource.",
|
||||
"examples": ["frontend-platform", "user:john.johnson"],
|
||||
"minLength": 1
|
||||
},
|
||||
"system": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the system that the AI resource belongs to.",
|
||||
"minLength": 1
|
||||
},
|
||||
"disciplines": {
|
||||
"type": "array",
|
||||
"description": "Engineering disciplines this rule applies to.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category of this rule.",
|
||||
"examples": ["security", "architecture", "style"],
|
||||
"minLength": 1
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string",
|
||||
"description": "Explanation of why this rule exists.",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "AiResourceV1alpha1",
|
||||
"description": "An AI resource represents contextual information consumed by AI coding tools, such as skills and rules.",
|
||||
"examples": [
|
||||
{
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "AiResource",
|
||||
"metadata": {
|
||||
"name": "frontend-design",
|
||||
"description": "Skill for creating production-grade frontend interfaces"
|
||||
},
|
||||
"spec": {
|
||||
"type": "skill",
|
||||
"lifecycle": "production",
|
||||
"owner": "ai-platform-team",
|
||||
"system": "ai-tooling"
|
||||
}
|
||||
}
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "Entity"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["spec"],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"enum": ["backstage.io/v1alpha1"]
|
||||
},
|
||||
"kind": {
|
||||
"enum": ["AiResource"]
|
||||
},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"required": ["type", "lifecycle", "owner"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of AI resource.",
|
||||
"examples": ["skill", "rule"],
|
||||
"minLength": 1
|
||||
},
|
||||
"lifecycle": {
|
||||
"type": "string",
|
||||
"description": "The lifecycle state of the AI resource.",
|
||||
"examples": ["experimental", "production", "deprecated"],
|
||||
"minLength": 1
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the owner of the AI resource.",
|
||||
"examples": ["ai-platform-team", "user:john.johnson"],
|
||||
"minLength": 1
|
||||
},
|
||||
"system": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the system that the AI resource belongs to.",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "AiResourceV1alpha1Skill",
|
||||
"description": "An AI resource of type 'skill', representing reusable contextual knowledge consumed by AI coding tools.",
|
||||
"examples": [
|
||||
{
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "AiResource",
|
||||
"metadata": {
|
||||
"name": "frontend-design",
|
||||
"description": "Skill for creating production-grade frontend interfaces"
|
||||
},
|
||||
"spec": {
|
||||
"type": "skill",
|
||||
"lifecycle": "production",
|
||||
"owner": "ai-platform-team",
|
||||
"system": "ai-tooling",
|
||||
"disciplines": ["web"],
|
||||
"categories": ["framework"],
|
||||
"agents": ["claude-code"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "Entity"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["spec"],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"enum": ["backstage.io/v1alpha1"]
|
||||
},
|
||||
"kind": {
|
||||
"enum": ["AiResource"]
|
||||
},
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"required": ["type", "lifecycle", "owner"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["skill"],
|
||||
"description": "The type of AI resource."
|
||||
},
|
||||
"lifecycle": {
|
||||
"type": "string",
|
||||
"description": "The lifecycle state of the AI resource.",
|
||||
"examples": ["experimental", "production", "deprecated"],
|
||||
"minLength": 1
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the owner of the AI resource.",
|
||||
"examples": ["ai-platform-team", "user:john.johnson"],
|
||||
"minLength": 1
|
||||
},
|
||||
"system": {
|
||||
"type": "string",
|
||||
"description": "An entity reference to the system that the AI resource belongs to.",
|
||||
"minLength": 1
|
||||
},
|
||||
"disciplines": {
|
||||
"type": "array",
|
||||
"description": "Engineering disciplines this skill applies to.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"description": "Descriptive categories for the skill.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"type": "array",
|
||||
"description": "AI coding tools this skill is designed for.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"dependsOn": {
|
||||
"type": "array",
|
||||
"description": "Entity references to other AI resources this skill depends on.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,45 @@
|
||||
# @backstage/plugin-catalog-backend-module-ai-model
|
||||
|
||||
Adds support for the `AiResource` entity kind to the catalog backend plugin. AI resources represent contextual information consumed by AI coding tools, such as skills and rules.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the module to your backend:
|
||||
|
||||
```ts
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-ai-model'));
|
||||
```
|
||||
|
||||
## Entity shape
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: AiResource
|
||||
metadata:
|
||||
name: frontend-design
|
||||
description: Skill for creating production-grade frontend interfaces
|
||||
spec:
|
||||
type: skill
|
||||
lifecycle: production
|
||||
owner: ai-platform-team
|
||||
system: ai-tooling
|
||||
disciplines:
|
||||
- web
|
||||
categories:
|
||||
- framework
|
||||
agents:
|
||||
- claude-code
|
||||
dependsOn:
|
||||
- airesource:default/base-coding-standards
|
||||
```
|
||||
|
||||
The `type` field determines which spec fields are available. Currently supported types:
|
||||
|
||||
- **`skill`** — reusable contextual knowledge for AI coding tools. Supports additional fields: `disciplines`, `categories`, `agents`, `dependsOn`.
|
||||
- **`rule`** — governance rules and constraints for AI coding tools. Supports additional fields: `disciplines`, `category` (required), `rationale` (required).
|
||||
|
||||
Any other `type` value is accepted with the base spec fields: `type`, `lifecycle`, `owner`, and optionally `system`.
|
||||
|
||||
## Accessing skill and rule content
|
||||
|
||||
The actual content of skills and rules is not stored in the entity spec. Instead, the source file is referenced via the standard `backstage.io/source-location` annotation, consistent with how other Backstage entities reference their source files. Entity providers that generate `AiResource` entities from `SKILL.md` or rule files should set this annotation to point to the source file.
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-plugin-catalog-backend-module-ai-model
|
||||
title: '@backstage/plugin-catalog-backend-module-ai-model'
|
||||
description: Adds support for the AIResource entity kind to the catalog backend plugin.
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-backend-plugin-module
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-ai-model",
|
||||
"version": "0.0.0",
|
||||
"description": "Adds support for the AiResource entity kind to the catalog backend plugin.",
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module",
|
||||
"pluginId": "catalog",
|
||||
"pluginPackage": "@backstage/plugin-catalog-backend"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/catalog-backend-module-ai-model"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
## API Report File for "@backstage/plugin-catalog-backend-module-ai-model"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
const catalogModuleAiResourceEntityModel: BackendFeature;
|
||||
export default catalogModuleAiResourceEntityModel;
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds support for the AiResource entity kind to the catalog backend plugin.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { catalogModuleAiResourceEntityModel as default } from './module';
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2026 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 { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { catalogModuleAiResourceEntityModel } from './module';
|
||||
|
||||
describe('catalogModuleAiResourceEntityModel', () => {
|
||||
it('should register the model source', async () => {
|
||||
const extensionPoint = {
|
||||
setFieldValidators: jest.fn(),
|
||||
setEntityDataParser: jest.fn(),
|
||||
addModelSource: jest.fn(),
|
||||
};
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogModelExtensionPoint, extensionPoint]],
|
||||
features: [catalogModuleAiResourceEntityModel],
|
||||
});
|
||||
|
||||
expect(extensionPoint.addModelSource).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2026 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 { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
CatalogModelSources,
|
||||
aiResourceEntityModel,
|
||||
} from '@backstage/catalog-model/alpha';
|
||||
import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
|
||||
/**
|
||||
* Registers support for the AiResource entity kind in the catalog.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const catalogModuleAiResourceEntityModel = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'ai-model',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
model: catalogModelExtensionPoint,
|
||||
},
|
||||
async init({ model }) {
|
||||
model.addModelSource(
|
||||
CatalogModelSources.static([aiResourceEntityModel]),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -4759,6 +4759,18 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-catalog-backend-module-ai-model@workspace:^, @backstage/plugin-catalog-backend-module-ai-model@workspace:plugins/catalog-backend-module-ai-model":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-catalog-backend-module-ai-model@workspace:plugins/catalog-backend-module-ai-model"
|
||||
dependencies:
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/plugin-catalog-node": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-catalog-backend-module-aws@workspace:plugins/catalog-backend-module-aws":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-catalog-backend-module-aws@workspace:plugins/catalog-backend-module-aws"
|
||||
@@ -29292,6 +29304,7 @@ __metadata:
|
||||
"@backstage/plugin-auth-backend-module-openshift-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-ai-model": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-logs": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend-module-openapi": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user