@backstage-community/plugin-catalog-backend-module-keycloak 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @backstage-community/plugin-catalog-backend-module-keycloak
2
2
 
3
+ ## 3.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4fc4345: Backstage version bump to v1.49.3
8
+
3
9
  ## 3.18.0
4
10
 
5
11
  ### Minor Changes
@@ -14,27 +14,8 @@ var authenticate = require('../lib/authenticate.cjs.js');
14
14
 
15
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
16
16
 
17
- function _interopNamespaceCompat(e) {
18
- if (e && typeof e === 'object' && 'default' in e) return e;
19
- var n = Object.create(null);
20
- if (e) {
21
- Object.keys(e).forEach(function (k) {
22
- if (k !== 'default') {
23
- var d = Object.getOwnPropertyDescriptor(e, k);
24
- Object.defineProperty(n, k, d.get ? d : {
25
- enumerable: true,
26
- get: function () { return e[k]; }
27
- });
28
- }
29
- });
30
- }
31
- n.default = e;
32
- return Object.freeze(n);
33
- }
34
-
35
17
  var KeyCloakAdminClient__default = /*#__PURE__*/_interopDefaultCompat(KeyCloakAdminClient);
36
18
  var pLimit__default = /*#__PURE__*/_interopDefaultCompat(pLimit);
37
- var uuid__namespace = /*#__PURE__*/_interopNamespaceCompat(uuid);
38
19
 
39
20
  const withLocations = (baseUrl, realm, entity) => {
40
21
  const kind = entity.kind === "Group" ? "groups" : "users";
@@ -63,6 +44,7 @@ class KeycloakOrgEntityProvider {
63
44
  );
64
45
  this.schedule(options.taskRunner);
65
46
  }
47
+ options;
66
48
  connection;
67
49
  meter;
68
50
  counter;
@@ -171,7 +153,7 @@ class KeycloakOrgEntityProvider {
171
153
  await taskRunner.run({
172
154
  id,
173
155
  fn: async () => {
174
- const taskInstanceId = uuid__namespace.v4();
156
+ const taskInstanceId = uuid.v4();
175
157
  const logger = this.options.logger.child({
176
158
  class: KeycloakOrgEntityProvider.prototype.constructor.name,
177
159
  taskId: id,
@@ -1 +1 @@
1
- {"version":3,"file":"KeycloakOrgEntityProvider.cjs.js","sources":["../../src/providers/KeycloakOrgEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n LoggerService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n type Entity,\n} from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\nimport type {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\nimport KeyCloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { Attributes, Counter, Meter, metrics } from '@opentelemetry/api';\n// @ts-ignore\nimport { merge } from 'lodash';\nimport pLimit from 'p-limit';\nimport * as uuid from 'uuid';\n\nimport {\n GroupTransformer,\n KEYCLOAK_ID_ANNOTATION,\n KeycloakProviderConfig,\n UserTransformer,\n} from '../lib';\nimport { readProviderConfigs } from '../lib/config';\nimport { readKeycloakRealm } from '../lib/read';\nimport { authenticate } from '../lib/authenticate';\n\n/**\n * Options for {@link KeycloakOrgEntityProvider}.\n *\n * @public\n */\nexport interface KeycloakOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The refresh schedule to use.\n * @remarks\n *\n * You can pass in the result of\n * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule?: SchedulerServiceTaskRunner;\n\n /**\n * Scheduler used to schedule refreshes based on\n * the schedule config.\n */\n scheduler?: SchedulerService;\n\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n// Makes sure that emitted entities have a proper location\nexport const withLocations = (\n baseUrl: string,\n realm: string,\n entity: Entity,\n): Entity => {\n const kind = entity.kind === 'Group' ? 'groups' : 'users';\n const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[KEYCLOAK_ID_ANNOTATION]}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n};\n\n/**\n * Ingests org data (users and groups) from GitHub.\n *\n * @public\n */\nexport class KeycloakOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private meter: Meter;\n private counter: Counter<Attributes>;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\n */\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n options: (\n | { schedule: SchedulerServiceTaskRunner }\n | { scheduler: SchedulerService }\n ) & {\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ): KeycloakOrgEntityProvider[] {\n const { config, logger } = deps;\n return readProviderConfigs(config).map(providerConfig => {\n let taskRunner: SchedulerServiceTaskRunner | string;\n if ('scheduler' in options && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if ('schedule' in options) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n throw new InputError(\n `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`,\n );\n }\n\n const provider = new KeycloakOrgEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: logger,\n taskRunner: taskRunner,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n });\n\n return provider;\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: KeycloakProviderConfig;\n logger: LoggerService;\n taskRunner: SchedulerServiceTaskRunner;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {\n this.meter = metrics.getMeter('default');\n this.counter = this.meter.createCounter(\n 'backend_keycloak.fetch.task.failure.count',\n {\n description:\n 'Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error.',\n },\n );\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\n */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options: { logger?: LoggerService; taskInstanceId: string }) {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n\n const { markReadComplete } = trackProgress(logger);\n\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const concurrency = provider.maxConcurrency ?? 20;\n const limit = pLimit(concurrency);\n\n const dataBatchFailureCounter = this.meter.createCounter(\n 'backend_keycloak.fetch.data.batch.failure.count',\n {\n description:\n 'Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task.',\n },\n );\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n options.taskInstanceId,\n dataBatchFailureCounter,\n {\n userQuerySize: provider.userQuerySize,\n groupQuerySize: provider.groupQuerySize,\n userTransformer: this.options.userTransformer,\n groupTransformer: this.options.groupTransformer,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `keycloak-org-provider:${this.options.id}`,\n entity: withLocations(provider.baseUrl, provider.realm, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\n */\n schedule(taskRunner: SchedulerServiceTaskRunner) {\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await taskRunner.run({\n id,\n fn: async () => {\n const taskInstanceId = uuid.v4();\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: taskInstanceId,\n });\n\n try {\n await this.read({ logger, taskInstanceId });\n } catch (error) {\n this.counter.add(1, { taskInstanceId: taskInstanceId });\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n logger.error('Error while syncing Keycloak users and groups', {\n // Default Error properties:\n name: error.name,\n cause: error.cause,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n });\n }\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: LoggerService) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading Keycloak users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n"],"names":["KEYCLOAK_ID_ANNOTATION","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","metrics","config","readProviderConfigs","InputError","NotFoundError","KeyCloakAdminClient","authenticate","pLimit","readKeycloakRealm","uuid","isError"],"mappings":"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"}
1
+ {"version":3,"file":"KeycloakOrgEntityProvider.cjs.js","sources":["../../src/providers/KeycloakOrgEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n LoggerService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n type Entity,\n} from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\nimport type {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\nimport KeyCloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { Attributes, Counter, Meter, metrics } from '@opentelemetry/api';\n// @ts-ignore\nimport { merge } from 'lodash';\nimport pLimit from 'p-limit';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport {\n GroupTransformer,\n KEYCLOAK_ID_ANNOTATION,\n KeycloakProviderConfig,\n UserTransformer,\n} from '../lib';\nimport { readProviderConfigs } from '../lib/config';\nimport { readKeycloakRealm } from '../lib/read';\nimport { authenticate } from '../lib/authenticate';\n\n/**\n * Options for {@link KeycloakOrgEntityProvider}.\n *\n * @public\n */\nexport interface KeycloakOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The refresh schedule to use.\n * @remarks\n *\n * You can pass in the result of\n * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule?: SchedulerServiceTaskRunner;\n\n /**\n * Scheduler used to schedule refreshes based on\n * the schedule config.\n */\n scheduler?: SchedulerService;\n\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n// Makes sure that emitted entities have a proper location\nexport const withLocations = (\n baseUrl: string,\n realm: string,\n entity: Entity,\n): Entity => {\n const kind = entity.kind === 'Group' ? 'groups' : 'users';\n const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[KEYCLOAK_ID_ANNOTATION]}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n};\n\n/**\n * Ingests org data (users and groups) from GitHub.\n *\n * @public\n */\nexport class KeycloakOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private meter: Meter;\n private counter: Counter<Attributes>;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\n */\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n options: (\n | { schedule: SchedulerServiceTaskRunner }\n | { scheduler: SchedulerService }\n ) & {\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ): KeycloakOrgEntityProvider[] {\n const { config, logger } = deps;\n return readProviderConfigs(config).map(providerConfig => {\n let taskRunner: SchedulerServiceTaskRunner | string;\n if ('scheduler' in options && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if ('schedule' in options) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n throw new InputError(\n `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`,\n );\n }\n\n const provider = new KeycloakOrgEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: logger,\n taskRunner: taskRunner,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n });\n\n return provider;\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: KeycloakProviderConfig;\n logger: LoggerService;\n taskRunner: SchedulerServiceTaskRunner;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {\n this.meter = metrics.getMeter('default');\n this.counter = this.meter.createCounter(\n 'backend_keycloak.fetch.task.failure.count',\n {\n description:\n 'Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error.',\n },\n );\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\n */\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options: { logger?: LoggerService; taskInstanceId: string }) {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n\n const { markReadComplete } = trackProgress(logger);\n\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const concurrency = provider.maxConcurrency ?? 20;\n const limit = pLimit(concurrency);\n\n const dataBatchFailureCounter = this.meter.createCounter(\n 'backend_keycloak.fetch.data.batch.failure.count',\n {\n description:\n 'Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task.',\n },\n );\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n options.taskInstanceId,\n dataBatchFailureCounter,\n {\n userQuerySize: provider.userQuerySize,\n groupQuerySize: provider.groupQuerySize,\n userTransformer: this.options.userTransformer,\n groupTransformer: this.options.groupTransformer,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `keycloak-org-provider:${this.options.id}`,\n entity: withLocations(provider.baseUrl, provider.realm, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\n */\n schedule(taskRunner: SchedulerServiceTaskRunner) {\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await taskRunner.run({\n id,\n fn: async () => {\n const taskInstanceId = uuidv4();\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: taskInstanceId,\n });\n\n try {\n await this.read({ logger, taskInstanceId });\n } catch (error) {\n this.counter.add(1, { taskInstanceId: taskInstanceId });\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n logger.error('Error while syncing Keycloak users and groups', {\n // Default Error properties:\n name: error.name,\n cause: error.cause,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n });\n }\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: LoggerService) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading Keycloak users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n"],"names":["KEYCLOAK_ID_ANNOTATION","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","metrics","config","readProviderConfigs","InputError","NotFoundError","KeyCloakAdminClient","authenticate","pLimit","readKeycloakRealm","uuidv4","isError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-catalog-backend-module-keycloak",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "description": "A Backend backend plugin for Keycloak",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "types": "./dist/index.d.ts",
@@ -46,27 +46,26 @@
46
46
  "tsc": "tsc"
47
47
  },
48
48
  "dependencies": {
49
- "@backstage/backend-plugin-api": "^1.7.0",
50
- "@backstage/catalog-model": "^1.7.6",
49
+ "@backstage/backend-plugin-api": "^1.8.0",
50
+ "@backstage/catalog-model": "^1.7.7",
51
51
  "@backstage/errors": "^1.2.7",
52
- "@backstage/plugin-catalog-backend-module-logs": "^0.1.19",
53
- "@backstage/plugin-catalog-node": "^2.0.0",
52
+ "@backstage/plugin-catalog-backend-module-logs": "^0.1.20",
53
+ "@backstage/plugin-catalog-node": "^2.1.0",
54
54
  "@keycloak/keycloak-admin-client": "26.5.6",
55
55
  "@opentelemetry/api": "^1.9.0",
56
56
  "jsonwebtoken": "^9.0.0",
57
57
  "lodash": "^4.17.21",
58
58
  "p-limit": "^6.1.0",
59
- "uuid": "^9.0.1"
59
+ "uuid": "^13.0.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@backstage/backend-defaults": "^0.15.2",
63
- "@backstage/backend-test-utils": "^1.11.0",
64
- "@backstage/cli": "^0.35.4",
62
+ "@backstage/backend-defaults": "^0.16.0",
63
+ "@backstage/backend-test-utils": "^1.11.1",
64
+ "@backstage/cli": "^0.36.0",
65
65
  "@backstage/config": "^1.3.6",
66
- "@backstage/plugin-catalog-backend": "^3.4.0",
66
+ "@backstage/plugin-catalog-backend": "^3.5.0",
67
67
  "@backstage/types": "^1.2.2",
68
68
  "@types/lodash": "4.17.24",
69
- "@types/uuid": "10.0.0",
70
69
  "deepmerge": "4.3.1",
71
70
  "open-cli": "^8.0.0"
72
71
  },