From 3664148c2ebb6c573c3bd376be7dcd9d22645e6a Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 15 May 2026 08:49:01 +0200 Subject: [PATCH] feat: add AIResource catalog entity kind as opt-in module Introduces @backstage/plugin-catalog-backend-module-ai-resource-entity-model, a new backend module that registers the AIResource kind with the catalog. Signed-off-by: benjdlambert --- .changeset/add-ai-resource-entity-model.md | 5 + .../.eslintrc.js | 1 + .../catalog-info.yaml | 10 ++ .../package.json | 58 +++++++ .../report-alpha.api.md | 12 ++ .../report.api.md | 36 +++++ .../src/AIResourceEntityV1alpha1.test.ts | 145 ++++++++++++++++++ .../src/AIResourceEntityV1alpha1.ts | 108 +++++++++++++ .../src/alpha.ts | 17 ++ .../src/index.ts | 28 ++++ .../src/module.test.ts | 36 +++++ .../src/module.ts | 42 +++++ .../schema/AIResource.v1alpha1.schema.json | 67 ++++++++ yarn.lock | 13 ++ 14 files changed, 578 insertions(+) create mode 100644 .changeset/add-ai-resource-entity-model.md create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/.eslintrc.js create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/catalog-info.yaml create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/package.json create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/report-alpha.api.md create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/report.api.md create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.test.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/alpha.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/index.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/module.test.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/module.ts create mode 100644 plugins/catalog-backend-module-ai-resource-entity-model/src/schema/AIResource.v1alpha1.schema.json diff --git a/.changeset/add-ai-resource-entity-model.md b/.changeset/add-ai-resource-entity-model.md new file mode 100644 index 0000000000..9a1d853471 --- /dev/null +++ b/.changeset/add-ai-resource-entity-model.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ai-resource-entity-model': minor +--- + +Introduced the `AIResource` catalog entity kind as an opt-in backend module. Install this module to register support for `AIResource` entities in your catalog, used to represent contextual information consumed by AI coding tools such as skills and rules. diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/.eslintrc.js b/plugins/catalog-backend-module-ai-resource-entity-model/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/catalog-info.yaml b/plugins/catalog-backend-module-ai-resource-entity-model/catalog-info.yaml new file mode 100644 index 0000000000..d6a29b6e18 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-ai-resource-entity-model + title: '@backstage/plugin-catalog-backend-module-ai-resource-entity-model' + description: Adds support for the AIResource entity kind to the catalog backend plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/package.json b/plugins/catalog-backend-module-ai-resource-entity-model/package.json new file mode 100644 index 0000000000..2ebd5b4bd6 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/package.json @@ -0,0 +1,58 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-ai-resource-entity-model", + "version": "0.1.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-resource-entity-model" + }, + "license": "Apache-2.0", + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "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:^", + "@backstage/types": "workspace:^" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^" + } +} diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/report-alpha.api.md b/plugins/catalog-backend-module-ai-resource-entity-model/report-alpha.api.md new file mode 100644 index 0000000000..3012dbbfbb --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/report-alpha.api.md @@ -0,0 +1,12 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-ai-resource-entity-model" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { CatalogModelLayer } from '@backstage/catalog-model/alpha'; + +// @alpha +export const aiResourceEntityModel: CatalogModelLayer; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/report.api.md b/plugins/catalog-backend-module-ai-resource-entity-model/report.api.md new file mode 100644 index 0000000000..6133a5d7a9 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/report.api.md @@ -0,0 +1,36 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-ai-resource-entity-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'; +import { Entity } from '@backstage/catalog-model'; +import { KindValidator } from '@backstage/catalog-model'; + +// @public +export interface AIResourceEntityV1alpha1 extends Entity { + // (undocumented) + apiVersion: 'backstage.io/v1alpha1'; + // (undocumented) + kind: 'AIResource'; + // (undocumented) + spec: { + type: string; + lifecycle: string; + owner: string; + system?: string; + }; +} + +// @public +export const aiResourceEntityV1alpha1Validator: KindValidator; + +// @public +const catalogModuleAIResourceEntityModel: BackendFeature; +export default catalogModuleAIResourceEntityModel; + +// @public +export const isAIResourceEntity: ( + entity: Entity, +) => entity is AIResourceEntityV1alpha1; +``` diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.test.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.test.ts new file mode 100644 index 0000000000..1de67dda74 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.test.ts @@ -0,0 +1,145 @@ +/* + * 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 '@backstage/catalog-model'; +import { + type AIResourceEntityV1alpha1, + aiResourceEntityV1alpha1Validator as validator, + isAIResourceEntity, +} from './AIResourceEntityV1alpha1'; + +describe('AIResourceV1alpha1Validator', () => { + let entity: AIResourceEntityV1alpha1; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'AIResource', + metadata: { + name: 'frontend-design', + }, + spec: { + type: 'skill', + lifecycle: 'production', + owner: 'ai-platform-team', + system: 'ai-tooling', + }, + }; + }); + + it('accepts valid data', async () => { + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('ignores unknown apiVersion', async () => { + (entity as any).apiVersion = 'backstage.io/v1beta0'; + await expect(validator.check(entity)).resolves.toBe(false); + }); + + it('ignores unknown kind', async () => { + (entity as any).kind = 'Wizard'; + await expect(validator.check(entity)).resolves.toBe(false); + }); + + it('rejects missing type', async () => { + delete (entity as any).spec.type; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); + + it('rejects missing lifecycle', async () => { + delete (entity as any).spec.lifecycle; + await expect(validator.check(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects wrong lifecycle', async () => { + (entity as any).spec.lifecycle = 7; + await expect(validator.check(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects empty lifecycle', async () => { + (entity as any).spec.lifecycle = ''; + await expect(validator.check(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects missing owner', async () => { + delete (entity as any).spec.owner; + await expect(validator.check(entity)).rejects.toThrow(/owner/); + }); + + it('rejects wrong owner', async () => { + (entity as any).spec.owner = 7; + await expect(validator.check(entity)).rejects.toThrow(/owner/); + }); + + it('rejects empty owner', async () => { + (entity as any).spec.owner = ''; + await expect(validator.check(entity)).rejects.toThrow(/owner/); + }); + + it('accepts missing system', async () => { + delete (entity as any).spec.system; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects wrong system', async () => { + (entity as any).spec.system = 7; + await expect(validator.check(entity)).rejects.toThrow(/system/); + }); + + it('rejects empty system', async () => { + (entity as any).spec.system = ''; + await expect(validator.check(entity)).rejects.toThrow(/system/); + }); +}); + +describe('isAIResourceEntity', () => { + it('returns true for a valid AIResource entity', () => { + 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); + }); +}); diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.ts new file mode 100644 index 0000000000..b272ffb02a --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/AIResourceEntityV1alpha1.ts @@ -0,0 +1,108 @@ +/* + * 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, + entityKindSchemaValidator, + type KindValidator, +} from '@backstage/catalog-model'; +import { createCatalogModelLayer } from '@backstage/catalog-model/alpha'; +import type { JsonObject } from '@backstage/types'; +import jsonSchema from './schema/AIResource.v1alpha1.schema.json'; + +/** + * Backstage catalog AIResource kind Entity. Represents contextual information + * consumed by AI coding tools, such as skills and rules. + * + * @public + */ +export interface AIResourceEntityV1alpha1 extends Entity { + apiVersion: 'backstage.io/v1alpha1'; + kind: 'AIResource'; + spec: { + type: string; + lifecycle: string; + owner: string; + system?: string; + }; +} + +const validator = entityKindSchemaValidator(jsonSchema); + +/** + * Entity data validator for {@link AIResourceEntityV1alpha1}. + * + * @public + */ +export const aiResourceEntityV1alpha1Validator: KindValidator = { + async check(data: Entity) { + return validator(data) === data; + }, +}; + +/** + * Type guard for {@link AIResourceEntityV1alpha1}. + * + * @public + */ +export const isAIResourceEntity = ( + entity: Entity, +): entity is AIResourceEntityV1alpha1 => + entity.apiVersion === 'backstage.io/v1alpha1' && entity.kind === 'AIResource'; + +/** + * 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: [ + { + selector: { path: 'spec.owner' }, + relation: 'ownedBy', + defaultKind: 'Group', + defaultNamespace: 'inherit', + allowedKinds: ['Group', 'User'], + }, + { + selector: { path: 'spec.system' }, + relation: 'partOf', + defaultKind: 'System', + defaultNamespace: 'inherit', + }, + ], + schema: { + jsonSchema: jsonSchema as JsonObject, + }, + }, + ], + }); + }, +}); diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/alpha.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/alpha.ts new file mode 100644 index 0000000000..1878461df7 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/alpha.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { aiResourceEntityModel } from './AIResourceEntityV1alpha1'; diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/index.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/index.ts new file mode 100644 index 0000000000..cf1b636db1 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/index.ts @@ -0,0 +1,28 @@ +/* + * 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 type { AIResourceEntityV1alpha1 } from './AIResourceEntityV1alpha1'; +export { + aiResourceEntityV1alpha1Validator, + isAIResourceEntity, +} from './AIResourceEntityV1alpha1'; +export { catalogModuleAIResourceEntityModel as default } from './module'; diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/module.test.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/module.test.ts new file mode 100644 index 0000000000..030c164d86 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/module.test.ts @@ -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); + }); +}); diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/module.ts b/plugins/catalog-backend-module-ai-resource-entity-model/src/module.ts new file mode 100644 index 0000000000..6ccab581df --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/module.ts @@ -0,0 +1,42 @@ +/* + * 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 } from '@backstage/catalog-model/alpha'; +import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { aiResourceEntityModel } from './AIResourceEntityV1alpha1'; + +/** + * Registers support for the AIResource entity kind in the catalog. + * + * @public + */ +export const catalogModuleAIResourceEntityModel = createBackendModule({ + pluginId: 'catalog', + moduleId: 'ai-resource-entity-model', + register(reg) { + reg.registerInit({ + deps: { + model: catalogModelExtensionPoint, + }, + async init({ model }) { + model.addModelSource( + CatalogModelSources.static([aiResourceEntityModel]), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-ai-resource-entity-model/src/schema/AIResource.v1alpha1.schema.json b/plugins/catalog-backend-module-ai-resource-entity-model/src/schema/AIResource.v1alpha1.schema.json new file mode 100644 index 0000000000..2b84c00fd1 --- /dev/null +++ b/plugins/catalog-backend-module-ai-resource-entity-model/src/schema/AIResource.v1alpha1.schema.json @@ -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 + } + } + } + } + } + ] +} diff --git a/yarn.lock b/yarn.lock index 821e3bfb8c..2737bf4ac8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4759,6 +4759,19 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-catalog-backend-module-ai-resource-entity-model@workspace:plugins/catalog-backend-module-ai-resource-entity-model": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-ai-resource-entity-model@workspace:plugins/catalog-backend-module-ai-resource-entity-model" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/types": "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"