remove some usages of the old alpha catalog service

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2025-06-05 11:37:23 +02:00
parent a58e48e983
commit eb1ee0331e
11 changed files with 107 additions and 124 deletions
@@ -18,10 +18,8 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import {
catalogProcessingExtensionPoint,
catalogServiceRef,
} from '@backstage/plugin-catalog-node/alpha';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { BitbucketCloudEntityProvider } from '../providers/BitbucketCloudEntityProvider';
@@ -35,8 +33,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
env.registerInit({
deps: {
auth: coreServices.auth,
catalog: catalogProcessingExtensionPoint,
catalogApi: catalogServiceRef,
catalogProcessing: catalogProcessingExtensionPoint,
catalog: catalogServiceRef,
config: coreServices.rootConfig,
events: eventsServiceRef,
logger: coreServices.logger,
@@ -44,8 +42,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
},
async init({
auth,
catalogProcessing,
catalog,
catalogApi,
config,
events,
logger,
@@ -53,13 +51,13 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
}) {
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
auth,
catalogApi,
catalog,
events,
logger,
scheduler,
});
catalog.addEntityProvider(providers);
catalogProcessing.addEntityProvider(providers);
},
});
},
@@ -15,6 +15,7 @@
*/
import {
BackstageCredentials,
SchedulerServiceTaskInvocationDefinition,
SchedulerServiceTaskRunner,
} from '@backstage/backend-plugin-api';
@@ -201,13 +202,13 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('no provider config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const config = new ConfigReader({});
const events = DefaultEventsService.create({ logger });
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -217,12 +218,12 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('single simple provider config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const providers = BitbucketCloudEntityProvider.fromConfig(simpleConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -235,14 +236,14 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('fail without schedule and scheduler', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
expect(() =>
BitbucketCloudEntityProvider.fromConfig(simpleConfig, {
auth,
catalogApi,
catalog,
events,
logger,
}),
@@ -250,8 +251,8 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('fail with scheduler but no schedule config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const scheduler = mockServices.scheduler.mock();
const config = new ConfigReader({
@@ -267,7 +268,7 @@ describe('BitbucketCloudEntityProvider', () => {
expect(() =>
BitbucketCloudEntityProvider.fromConfig(config, {
auth,
catalogApi,
catalog,
events,
logger,
scheduler,
@@ -278,8 +279,8 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('single simple provider config with schedule in config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const scheduler = mockServices.scheduler.mock();
const config = new ConfigReader({
@@ -298,7 +299,7 @@ describe('BitbucketCloudEntityProvider', () => {
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
auth,
catalogApi,
catalog,
events,
logger,
scheduler,
@@ -311,8 +312,8 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('multiple provider configs', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const config = new ConfigReader({
catalog: {
providers: {
@@ -330,7 +331,7 @@ describe('BitbucketCloudEntityProvider', () => {
const events = DefaultEventsService.create({ logger });
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -346,12 +347,12 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('apply full update on scheduled execution', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
const auth = mockServices.auth();
const catalog = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -559,17 +560,14 @@ describe('BitbucketCloudEntityProvider', () => {
'added-module/catalog-custom.yaml',
);
const auth = mockServices.auth.mock({
getPluginRequestToken: async () => ({ token: 'fake-token' }),
});
const auth = mockServices.auth();
const events = DefaultEventsService.create({ logger });
const catalogApi = catalogServiceMock.mock({
const catalog = catalogServiceMock.mock({
getEntities: async (
request: { filter: Record<string, string> },
options: { token: string },
_options: { credentials: BackstageCredentials },
): Promise<{ items: Entity[] }> => {
if (
options.token !== 'fake-token' ||
request.filter.kind !== 'Location' ||
request.filter['metadata.annotations.bitbucket.org/repo-url'] !==
'https://bitbucket.org/test-ws/test-repo'
@@ -584,7 +582,7 @@ describe('BitbucketCloudEntityProvider', () => {
});
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -695,13 +693,13 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('no onRepoPush update on non-matching workspace slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const auth = mockServices.auth();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -722,18 +720,18 @@ describe('BitbucketCloudEntityProvider', () => {
},
});
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(catalog.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
it('no onRepoPush update on non-matching repo slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const auth = mockServices.auth();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -751,7 +749,7 @@ describe('BitbucketCloudEntityProvider', () => {
},
});
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(catalog.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
@@ -767,17 +765,14 @@ describe('BitbucketCloudEntityProvider', () => {
'module/catalog-custom.yaml',
);
const auth = mockServices.auth.mock({
getPluginRequestToken: async () => ({ token: 'fake-token' }),
});
const auth = mockServices.auth();
const events = DefaultEventsService.create({ logger });
const catalogApi = catalogServiceMock.mock({
const catalog = catalogServiceMock.mock({
getEntities: async (
request: { filter: Record<string, string> },
options: { token: string },
_options: { credentials: BackstageCredentials },
): Promise<{ items: Entity[] }> => {
if (
options.token !== 'fake-token' ||
request.filter.kind !== 'Location' ||
request.filter['metadata.annotations.bitbucket.org/repo-url'] !==
'https://bitbucket.org/test-ws/test-repo-old'
@@ -792,7 +787,7 @@ describe('BitbucketCloudEntityProvider', () => {
});
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -870,13 +865,13 @@ describe('BitbucketCloudEntityProvider', () => {
});
it('no onRepoUpdated update on non-matching workspace slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const auth = mockServices.auth();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -897,18 +892,18 @@ describe('BitbucketCloudEntityProvider', () => {
},
});
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(catalog.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
it('no onRepoUpdated update on non-matching repo slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const auth = mockServices.auth();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -926,18 +921,18 @@ describe('BitbucketCloudEntityProvider', () => {
},
});
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(catalog.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
it('no onRepoUpdated update on non-relevant repo update', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const auth = mockServices.auth();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
catalogApi,
catalog,
events,
logger,
schedule,
@@ -957,7 +952,7 @@ describe('BitbucketCloudEntityProvider', () => {
},
});
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(catalog.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
});
@@ -20,7 +20,6 @@ import {
SchedulerService,
SchedulerServiceTaskRunner,
} from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { LocationEntity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
@@ -33,6 +32,7 @@ import {
Models,
} from '@backstage/plugin-bitbucket-cloud-common';
import {
CatalogService,
DeferredEntity,
EntityProvider,
EntityProviderConnection,
@@ -68,7 +68,7 @@ interface IngestionTarget {
*/
export class BitbucketCloudEntityProvider implements EntityProvider {
private readonly auth: AuthService;
private readonly catalogApi: CatalogApi;
private readonly catalog: CatalogService;
private readonly client: BitbucketCloudClient;
private readonly config: BitbucketCloudEntityProviderConfig;
private readonly events: EventsService;
@@ -81,7 +81,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
config: Config,
options: {
auth: AuthService;
catalogApi: CatalogApi;
catalog: CatalogService;
events: EventsService;
logger: LoggerService;
schedule?: SchedulerServiceTaskRunner;
@@ -113,7 +113,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
return new BitbucketCloudEntityProvider(
options.auth,
options.catalogApi,
options.catalog,
providerConfig,
options.events,
integration,
@@ -125,7 +125,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
private constructor(
auth: AuthService,
catalogApi: CatalogApi,
catalog: CatalogService,
config: BitbucketCloudEntityProviderConfig,
events: EventsService,
integration: BitbucketCloudIntegration,
@@ -133,7 +133,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
taskRunner: SchedulerServiceTaskRunner,
) {
this.auth = auth;
this.catalogApi = catalogApi;
this.catalog = catalog;
this.client = BitbucketCloudClient.fromConfig(integration.config);
this.config = config;
this.events = events;
@@ -350,13 +350,11 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
filter[`metadata.annotations.${ANNOTATION_BITBUCKET_CLOUD_REPO_URL}`] =
repoUrl;
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
return this.catalogApi
.getEntities({ filter }, { token })
return this.catalog
.getEntities(
{ filter },
{ credentials: await this.auth.getOwnServiceCredentials() },
)
.then(result => result.items) as Promise<LocationEntity[]>;
}
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node';
import { NotificationsEmailProcessor } from './processor';
import {
@@ -25,7 +25,7 @@ import {
} from '@backstage/backend-plugin-api';
import { Config, readDurationFromConfig } from '@backstage/config';
import { durationToMilliseconds } from '@backstage/types';
import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client';
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
import {
getProcessorFiltersFromConfig,
Notification,
@@ -39,6 +39,7 @@ import {
createStreamTransport,
} from './transports';
import { UserEntity } from '@backstage/catalog-model';
import { CatalogService } from '@backstage/plugin-catalog-node';
import { compact } from 'lodash';
import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node';
import { NotificationTemplateRenderer } from '../extensions';
@@ -62,7 +63,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
constructor(
private readonly logger: LoggerService,
private readonly config: Config,
private readonly catalog: CatalogApi,
private readonly catalog: CatalogService,
private readonly auth: AuthService,
private readonly cache?: CacheService,
private readonly templateRenderer?: NotificationTemplateRenderer,
@@ -152,10 +153,6 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
return cached;
}
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const entities = await this.catalog.getEntities(
{
filter: [
@@ -163,7 +160,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
],
fields: ['spec.profile.email'],
},
{ token },
{ credentials: await this.auth.getOwnServiceCredentials() },
);
const ret = compact([
...new Set(
@@ -188,11 +185,9 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
return cached;
}
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
const entity = await this.catalog.getEntityByRef(entityRef, {
credentials: await this.auth.getOwnServiceCredentials(),
});
const entity = await this.catalog.getEntityByRef(entityRef, { token });
const ret: string[] = [];
if (entity) {
const userEntity = entity as UserEntity;
@@ -40,7 +40,6 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
+1 -1
View File
@@ -25,7 +25,7 @@ import {
notificationsProcessingExtensionPoint,
NotificationsProcessingExtensionPoint,
} from '@backstage/plugin-notifications-node';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
class NotificationsProcessingExtensionPointImpl
implements NotificationsProcessingExtensionPoint
@@ -28,25 +28,25 @@ describe('getUsersForEntityRef', () => {
await expect(
getUsersForEntityRef(null, [], {
auth: mockServices.auth(),
catalogClient: catalogServiceMock(),
catalog: catalogServiceMock(),
}),
).resolves.toEqual([]);
});
it('should resolve users without calling catalog', async () => {
const catalogClient = catalogServiceMock();
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'getEntitiesByRefs');
await expect(
getUsersForEntityRef(['user:foo', 'user:ignored'], ['user:ignored'], {
auth: mockServices.auth(),
catalogClient,
catalog,
}),
).resolves.toEqual(['user:foo']);
expect(catalogClient.getEntitiesByRefs).not.toHaveBeenCalled();
expect(catalog.getEntitiesByRefs).not.toHaveBeenCalled();
});
it('should resolve group entities to users', async () => {
const catalogClient = catalogServiceMock({
const catalog = catalogServiceMock({
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
@@ -91,14 +91,14 @@ describe('getUsersForEntityRef', () => {
['user:default/ignored'],
{
auth: mockServices.auth(),
catalogClient,
catalog,
},
),
).resolves.toEqual(['user:default/foo', 'user:default/bar']);
});
it('should resolve user owner of entity from entity ref', async () => {
const catalogClient = catalogServiceMock({
const catalog = catalogServiceMock({
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
@@ -119,13 +119,13 @@ describe('getUsersForEntityRef', () => {
await expect(
getUsersForEntityRef('component:default/test_component', [], {
auth: mockServices.auth(),
catalogClient,
catalog,
}),
).resolves.toEqual(['user:default/foo']);
});
it('should resolve group owner of entity from entity ref', async () => {
const catalogClient = catalogServiceMock({
const catalog = catalogServiceMock({
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
@@ -159,7 +159,7 @@ describe('getUsersForEntityRef', () => {
await expect(
getUsersForEntityRef('component:default/test_component', [], {
auth: mockServices.auth(),
catalogClient,
catalog,
}),
).resolves.toEqual(['user:default/foo']);
});
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Entity,
isGroupEntity,
@@ -24,7 +25,7 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { AuthService } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogService } from '@backstage/plugin-catalog-node';
const isUserEntityRef = (ref: string) =>
parseEntityRef(ref).kind.toLocaleLowerCase() === 'user';
@@ -45,20 +46,15 @@ export const getUsersForEntityRef = async (
excludeEntityRefs: string | string[],
options: {
auth: AuthService;
catalogClient: CatalogApi;
catalog: CatalogService;
},
): Promise<string[]> => {
const { auth, catalogClient } = options;
const { auth, catalog } = options;
if (entityRef === null) {
return [];
}
const { token } = await auth.getPluginRequestToken({
onBehalfOf: await auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const excluded = Array.isArray(excludeEntityRefs)
? excludeEntityRefs
: [excludeEntityRefs];
@@ -71,12 +67,12 @@ export const getUsersForEntityRef = async (
const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations'];
let entities: Array<Entity | undefined> = [];
if (entityRefs.length > 0) {
const fetchedEntities = await catalogClient.getEntitiesByRefs(
const fetchedEntities = await catalog.getEntitiesByRefs(
{
entityRefs,
fields,
},
{ token },
{ credentials: await auth.getOwnServiceCredentials() },
);
entities = fetchedEntities.items;
}
@@ -114,12 +110,12 @@ export const getUsersForEntityRef = async (
let childGroupUsers: string[][] = [];
if (childGroupRefs.length > 0) {
const childGroups = await catalogClient.getEntitiesByRefs(
const childGroups = await catalog.getEntitiesByRefs(
{
entityRefs: childGroupRefs,
fields,
},
{ token },
{ credentials: await auth.getOwnServiceCredentials() },
);
childGroupUsers = await Promise.all(childGroups.items.map(mapEntity));
}
@@ -145,7 +141,9 @@ export const getUsersForEntityRef = async (
return [ownerRef];
}
const owner = await catalogClient.getEntityByRef(ownerRef, { token });
const owner = await catalog.getEntityByRef(ownerRef, {
credentials: await auth.getOwnServiceCredentials(),
});
return mapEntity(owner);
}
@@ -23,7 +23,7 @@ import {
TopicGetOptions,
} from '../database';
import { v4 as uuid } from 'uuid';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogService } from '@backstage/plugin-catalog-node';
import {
NotificationProcessor,
NotificationSendOptions,
@@ -63,7 +63,7 @@ export interface RouterOptions {
httpAuth: HttpAuthService;
userInfo: UserInfoService;
signals?: SignalsService;
catalog: CatalogApi;
catalog: CatalogService;
processors?: NotificationProcessor[];
}
@@ -672,7 +672,7 @@ export async function createRouter(
users = await getUsersForEntityRef(
entityRef,
recipients.excludeEntityRef ?? [],
{ auth, catalogClient: catalog },
{ auth, catalog },
);
} catch (e) {
throw new InputError('Failed to resolve notification receivers', e);
-1
View File
@@ -7082,7 +7082,6 @@ __metadata:
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/catalog-client": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"