diff --git a/packages/backend/package.json b/packages/backend/package.json index df0ee4838..64d1ebd65 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -47,6 +47,7 @@ "@backstage/plugin-search-backend-node": "backstage:^", "@backstage/plugin-techdocs-backend": "backstage:^", "@giantswarm/backstage-plugin-auth-backend-module-gs": "^0.8.0", + "@giantswarm/backstage-plugin-catalog-backend-module-gs": "^0.1.0", "@giantswarm/backstage-plugin-scaffolder-backend-module-gs": "^0.7.0", "@giantswarm/backstage-plugin-techdocs-backend-module-gs": "^0.7.0", "app": "link:../app", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 24b7effdf..129179ec7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -41,6 +41,7 @@ backend.add(import('@backstage/plugin-catalog-backend-module-aws')); backend.add( import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); +backend.add(import('@giantswarm/backstage-plugin-catalog-backend-module-gs')); // permission plugin backend.add(import('@backstage/plugin-permission-backend')); diff --git a/plugins/catalog-backend-module-gs/.eslintrc.js b/plugins/catalog-backend-module-gs/.eslintrc.js new file mode 100644 index 000000000..e2a53a6ad --- /dev/null +++ b/plugins/catalog-backend-module-gs/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-gs/README.md b/plugins/catalog-backend-module-gs/README.md new file mode 100644 index 000000000..6d2c71fe7 --- /dev/null +++ b/plugins/catalog-backend-module-gs/README.md @@ -0,0 +1,3 @@ +# @giantswarm/backstage-plugin-catalog-backend-module-gs + +The GS backend module for the catalog plugin. diff --git a/plugins/catalog-backend-module-gs/package.json b/plugins/catalog-backend-module-gs/package.json new file mode 100644 index 000000000..60f5e2a6b --- /dev/null +++ b/plugins/catalog-backend-module-gs/package.json @@ -0,0 +1,45 @@ +{ + "name": "@giantswarm/backstage-plugin-catalog-backend-module-gs", + "version": "0.1.0", + "license": "Apache-2.0", + "private": true, + "description": "The GS backend module for the catalog plugin.", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-plugin-api": "backstage:^", + "@backstage/catalog-model": "backstage:^", + "@backstage/config": "backstage:^", + "@backstage/integration": "backstage:^", + "@backstage/plugin-catalog-common": "backstage:^", + "@backstage/plugin-catalog-node": "backstage:^", + "@giantswarm/backstage-plugin-gs-common": "^0.12.0", + "@octokit/graphql": "^8.2.1", + "@types/lodash": "^4.17.5", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@backstage/backend-test-utils": "backstage:^", + "@backstage/cli": "backstage:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-backend-module-gs/src/index.ts b/plugins/catalog-backend-module-gs/src/index.ts new file mode 100644 index 000000000..10a3eab9c --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/index.ts @@ -0,0 +1,8 @@ +/***/ +/** + * The GS backend module for the catalog plugin. + * + * @packageDocumentation + */ + +export { catalogModuleGS as default } from './module'; diff --git a/plugins/catalog-backend-module-gs/src/module.ts b/plugins/catalog-backend-module-gs/src/module.ts new file mode 100644 index 000000000..d50523055 --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/module.ts @@ -0,0 +1,31 @@ +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { + ServiceDeploymentsProcessor, + ServiceReleaseInfoProcessor, +} from './processors'; + +export const catalogModuleGS = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gs', + register(reg) { + reg.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + logger: coreServices.logger, + }, + async init({ catalog, config, logger }) { + catalog.addProcessor([ + new ServiceDeploymentsProcessor(), + ServiceReleaseInfoProcessor.fromConfig(config, { + logger, + }), + ]); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.test.ts b/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.test.ts new file mode 100644 index 000000000..77d292ba3 --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.test.ts @@ -0,0 +1,90 @@ +import { ServiceDeploymentsProcessor } from './ServiceDeploymentsProcessor'; +import { Entity } from '@backstage/catalog-model'; +import { setupEntities } from './testUtils'; + +describe('ServiceDeploymentsProcessor', () => { + let processor: ServiceDeploymentsProcessor; + + beforeEach(() => { + processor = new ServiceDeploymentsProcessor(); + }); + + it('should return the entity unchanged if it is not a GS service', async () => { + const { component, website, service, api, system, user, group } = + setupEntities(); + + expect(await processor.preProcessEntity(component)).toEqual(component); + expect(await processor.preProcessEntity(website)).toEqual(website); + expect(await processor.preProcessEntity(service)).toEqual(service); + expect(await processor.preProcessEntity(api)).toEqual(api); + expect(await processor.preProcessEntity(system)).toEqual(system); + expect(await processor.preProcessEntity(user)).toEqual(user); + expect(await processor.preProcessEntity(group)).toEqual(group); + }); + + it('should process GS service entity and add GS_DEPLOYMENT_NAMES annotation', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'gs-service', + annotations: { + 'backstage.io/source-location': + 'url:https://github.com/giantswarm/gs-service', + }, + }, + spec: { + type: 'service', + }, + }; + + expect(await processor.preProcessEntity(entity)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'gs-service', + annotations: { + 'backstage.io/source-location': + 'url:https://github.com/giantswarm/gs-service', + 'giantswarm.io/deployment-names': 'gs-service,gs-service-app', + }, + }, + spec: { + type: 'service', + }, + }); + }); + + it('should handle entity name with "-app" suffix correctly', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'gs-service-app', + annotations: { + 'backstage.io/source-location': + 'url:https://github.com/giantswarm/gs-service', + }, + }, + spec: { + type: 'service', + }, + }; + + expect(await processor.preProcessEntity(entity)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'gs-service-app', + annotations: { + 'backstage.io/source-location': + 'url:https://github.com/giantswarm/gs-service', + 'giantswarm.io/deployment-names': 'gs-service,gs-service-app', + }, + }, + spec: { + type: 'service', + }, + }); + }); +}); diff --git a/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.ts b/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.ts new file mode 100644 index 000000000..d135f5c0d --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/ServiceDeploymentsProcessor.ts @@ -0,0 +1,33 @@ +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; +import { Entity } from '@backstage/catalog-model'; +import { GS_DEPLOYMENT_NAMES } from '@giantswarm/backstage-plugin-gs-common'; +import merge from 'lodash/merge'; +import { isGSService } from './utils'; + +export class ServiceDeploymentsProcessor implements CatalogProcessor { + getProcessorName(): string { + return 'ServiceDeploymentsProcessor'; + } + + async preProcessEntity(entity: Entity): Promise { + if (!isGSService(entity)) { + return entity; + } + + const nameWithoutAppSuffix = entity.metadata.name.replace(/-app$/, ''); + const nameWithAppSuffix = `${nameWithoutAppSuffix}-app`; + + const names = [nameWithoutAppSuffix, nameWithAppSuffix]; + + return merge( + { + metadata: { + annotations: { + [GS_DEPLOYMENT_NAMES]: names.join(','), + }, + }, + }, + entity, + ); + } +} diff --git a/plugins/catalog-backend-module-gs/src/processors/ServiceReleaseInfoProcessor.ts b/plugins/catalog-backend-module-gs/src/processors/ServiceReleaseInfoProcessor.ts new file mode 100644 index 000000000..0bcef6fbd --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/ServiceReleaseInfoProcessor.ts @@ -0,0 +1,130 @@ +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + GithubCredentialType, + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; +import { + GS_LATEST_RELEASE_DATE, + GS_LATEST_RELEASE_TAG, +} from '@giantswarm/backstage-plugin-gs-common'; +import { graphql } from '@octokit/graphql'; +import merge from 'lodash/merge'; +import { formatVersion, isGSService } from './utils'; +import { getRepositoryLatestRelease } from './github'; + +type GraphQL = typeof graphql; + +export class ServiceReleaseInfoProcessor implements CatalogProcessor { + private readonly integrations: ScmIntegrationRegistry; + private readonly logger: LoggerService; + private readonly githubCredentialsProvider: GithubCredentialsProvider; + + static fromConfig( + config: Config, + options: { + logger: LoggerService; + githubCredentialsProvider?: GithubCredentialsProvider; + }, + ) { + const integrations = ScmIntegrations.fromConfig(config); + + return new ServiceReleaseInfoProcessor({ + ...options, + integrations, + }); + } + + constructor(options: { + integrations: ScmIntegrationRegistry; + logger: LoggerService; + githubCredentialsProvider?: GithubCredentialsProvider; + }) { + this.integrations = options.integrations; + this.logger = options.logger; + this.githubCredentialsProvider = + options.githubCredentialsProvider || + DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); + } + + getProcessorName(): string { + return 'ServiceReleaseInfoProcessor'; + } + + async preProcessEntity(entity: Entity): Promise { + if (!isGSService(entity)) { + return entity; + } + + const sourceLocation = getEntitySourceLocation(entity); + + const { client } = await this.createClient(sourceLocation.target); + const { org, repoName } = parseGithubRepoUrl(sourceLocation.target); + + this.logger.info( + `Reading latest release information for ${sourceLocation.target}`, + ); + const response = await getRepositoryLatestRelease(client, org, repoName); + + if (!response) { + return entity; + } + + return merge( + { + metadata: { + annotations: { + [GS_LATEST_RELEASE_TAG]: formatVersion(response.name), + [GS_LATEST_RELEASE_DATE]: response.createdAt, + }, + }, + }, + entity, + ); + } + + private async createClient( + repoUrl: string, + ): Promise<{ client: GraphQL; tokenType: GithubCredentialType }> { + const gitHubConfig = this.integrations.github.byUrl(repoUrl)?.config; + + if (!gitHubConfig) { + throw new Error( + `There is no GitHub provider that matches ${repoUrl}. Please add a configuration for an integration.`, + ); + } + + const { headers, type: tokenType } = + await this.githubCredentialsProvider.getCredentials({ + url: repoUrl, + }); + + const client = graphql.defaults({ + baseUrl: gitHubConfig.apiBaseUrl, + headers, + }); + + return { client, tokenType }; + } +} + +export function parseGithubRepoUrl(urlString: string): { + org: string; + repoName: string; +} { + const path = new URL(urlString).pathname.slice(1).split('/'); + + if (path.length === 2 && path[0].length && path[1].length) { + return { + org: decodeURIComponent(path[0]), + repoName: decodeURIComponent(path[1]), + }; + } + + throw new Error(`Expected a URL pointing to //, got ${urlString}`); +} diff --git a/plugins/catalog-backend-module-gs/src/processors/github.ts b/plugins/catalog-backend-module-gs/src/processors/github.ts new file mode 100644 index 000000000..3a5eea5ff --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/github.ts @@ -0,0 +1,58 @@ +import { graphql } from '@octokit/graphql'; + +type GraphQL = typeof graphql; + +type QueryResponse = { + repositoryOwner?: RepositoryOwnerResponse; +}; + +type PageInfo = { + hasNextPage: boolean; + endCursor?: string; +}; + +type Connection = { + pageInfo: PageInfo; + nodes: T[]; +}; + +type RepositoryOwnerResponse = { + repositories?: Connection; + repository?: RepositoryResponse; +}; + +type RepositoryResponse = { + releases?: Connection; +}; + +export type ReleaseResponse = { + name: string; + createdAt: string; +}; + +export async function getRepositoryLatestRelease( + client: GraphQL, + org: string, + repoName: string, +): Promise { + const query = ` + query latestRelease($org: String!, $repoName: String!) { + repositoryOwner(login: $org) { + repository(name: $repoName) { + releases(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { + name + createdAt + } + } + } + } + }`; + + const response: QueryResponse = await client(query, { + org, + repoName, + }); + + return response.repositoryOwner?.repository?.releases?.nodes?.[0] || null; +} diff --git a/plugins/catalog-backend-module-gs/src/processors/index.ts b/plugins/catalog-backend-module-gs/src/processors/index.ts new file mode 100644 index 000000000..a7218b4f2 --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/index.ts @@ -0,0 +1,2 @@ +export { ServiceDeploymentsProcessor } from './ServiceDeploymentsProcessor'; +export { ServiceReleaseInfoProcessor } from './ServiceReleaseInfoProcessor'; diff --git a/plugins/catalog-backend-module-gs/src/processors/testUtils.ts b/plugins/catalog-backend-module-gs/src/processors/testUtils.ts new file mode 100644 index 000000000..5d3c726a6 --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/testUtils.ts @@ -0,0 +1,79 @@ +import { Entity } from '@backstage/catalog-model'; + +export function setupEntities() { + const component: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + }; + + const website: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-website', + }, + spec: { + type: 'website', + }, + }; + + const service: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-service', + annotations: { + 'backstage.io/source-location': + 'url:https://github.com/test-org/test-service', + }, + }, + spec: { + type: 'service', + }, + }; + + const api: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'test-api', + }, + }; + + const system: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'test-system', + }, + }; + + const user: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'test-user', + }, + }; + + const group: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'test-group', + }, + }; + + return { + component, + website, + service, + api, + system, + user, + group, + }; +} diff --git a/plugins/catalog-backend-module-gs/src/processors/utils.ts b/plugins/catalog-backend-module-gs/src/processors/utils.ts new file mode 100644 index 000000000..9984ecc77 --- /dev/null +++ b/plugins/catalog-backend-module-gs/src/processors/utils.ts @@ -0,0 +1,28 @@ +import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; + +const SERVICE_TYPE = 'service'; +const GS_ORG_NAME = 'giantswarm'; + +function extractGitHubOrgName(url: string): string | null { + const match = url.match(/^https:\/\/github\.com\/([^\/]+)\/[^\/]+/); + return match ? match[1] : null; +} + +export function isGSService(entity: Entity): boolean { + if (entity.spec?.type !== SERVICE_TYPE) { + return false; + } + + const sourceLocation = getEntitySourceLocation(entity); + const orgName = extractGitHubOrgName(sourceLocation.target); + if (!orgName) { + return false; + } + + return orgName === GS_ORG_NAME; +} + +export function formatVersion(version: string) { + // Remove the `v` prefix if it's present. + return version.startsWith('v') ? version.slice(1) : version; +} diff --git a/plugins/gs-common/package.json b/plugins/gs-common/package.json index da0534e13..07876b3d5 100644 --- a/plugins/gs-common/package.json +++ b/plugins/gs-common/package.json @@ -40,6 +40,7 @@ "dist" ], "dependencies": { + "@backstage/catalog-model": "^1.7.3", "date-fns": "^4.0.0", "date-fns-tz": "^3.0.0" }, diff --git a/plugins/gs/src/components/utils/entity.ts b/plugins/gs-common/src/entity.ts similarity index 98% rename from plugins/gs/src/components/utils/entity.ts rename to plugins/gs-common/src/entity.ts index 07fa802f5..7bc1fce62 100644 --- a/plugins/gs/src/components/utils/entity.ts +++ b/plugins/gs-common/src/entity.ts @@ -1,5 +1,5 @@ import { ANNOTATION_SOURCE_LOCATION, Entity } from '@backstage/catalog-model'; -import { formatVersion } from './helpers'; +import { formatVersion } from './utils'; export const GS_DEPLOYMENT_NAMES = 'giantswarm.io/deployment-names'; export const GS_INGRESS_HOST = 'giantswarm.io/ingress-host'; diff --git a/plugins/gs-common/src/index.ts b/plugins/gs-common/src/index.ts index 263c38c09..899b5f4f3 100644 --- a/plugins/gs-common/src/index.ts +++ b/plugins/gs-common/src/index.ts @@ -1,3 +1,4 @@ +export * from './entity'; export * from './api/types'; export * from './api/utils'; export * from './api/constants'; diff --git a/plugins/gs-common/src/utils.ts b/plugins/gs-common/src/utils.ts new file mode 100644 index 000000000..bec379a10 --- /dev/null +++ b/plugins/gs-common/src/utils.ts @@ -0,0 +1,4 @@ +export function formatVersion(version: string) { + // Remove the `v` prefix if it's present. + return version.startsWith('v') ? version.slice(1) : version; +} diff --git a/plugins/gs/src/components/catalog/CustomCatalogTable/CustomCatalogTable.tsx b/plugins/gs/src/components/catalog/CustomCatalogTable/CustomCatalogTable.tsx index 9448dc85b..7b6486fe2 100644 --- a/plugins/gs/src/components/catalog/CustomCatalogTable/CustomCatalogTable.tsx +++ b/plugins/gs/src/components/catalog/CustomCatalogTable/CustomCatalogTable.tsx @@ -22,7 +22,7 @@ import { columnFactories, hiddenColumn, noWrapColumn } from '../columns'; import { isEntityHelmChartsAvailable, isEntityLatestReleaseAvailable, -} from '../../utils/entity'; +} from '@giantswarm/backstage-plugin-gs-common'; const YellowStar = withStyles({ root: { diff --git a/plugins/gs/src/components/catalog/columns.tsx b/plugins/gs/src/components/catalog/columns.tsx index 20ea7a2b4..1ab4265f9 100644 --- a/plugins/gs/src/components/catalog/columns.tsx +++ b/plugins/gs/src/components/catalog/columns.tsx @@ -7,7 +7,7 @@ import { getHelmChartsFromEntity, getLatestReleaseDateFromEntity, getLatestReleaseTagFromEntity, -} from '../utils/entity'; +} from '@giantswarm/backstage-plugin-gs-common'; import { DateComponent } from '../UI'; import { compareDates } from '../utils/helpers'; import { Entity } from '@backstage/catalog-model'; diff --git a/plugins/gs/src/components/deployments/EntityDeploymentsContent/EntityDeploymentsContent.tsx b/plugins/gs/src/components/deployments/EntityDeploymentsContent/EntityDeploymentsContent.tsx index b66c054bd..ae8304808 100644 --- a/plugins/gs/src/components/deployments/EntityDeploymentsContent/EntityDeploymentsContent.tsx +++ b/plugins/gs/src/components/deployments/EntityDeploymentsContent/EntityDeploymentsContent.tsx @@ -11,7 +11,7 @@ import { getIngressHostFromEntity, getGrafanaDashboardFromEntity, getSourceLocationFromEntity, -} from '../../utils/entity'; +} from '@giantswarm/backstage-plugin-gs-common'; import { GSContext } from '../../GSContext'; import { InstallationsWrapper } from '../../InstallationsWrapper'; import { DetailsPane } from '../../UI'; diff --git a/plugins/gs/src/index.ts b/plugins/gs/src/index.ts index 07d4ddc0c..69096a683 100644 --- a/plugins/gs/src/index.ts +++ b/plugins/gs/src/index.ts @@ -28,5 +28,5 @@ export { isEntityHelmChartsAvailable as isEntityGSHelmChartsAvailable, isEntityInstallationResource as isEntityGSInstallationResource, isEntityKratixResource as isEntityGSKratixResource, -} from './components/utils/entity'; +} from '@giantswarm/backstage-plugin-gs-common'; export { columnFactories as GSColumnFactories } from './components/catalog/columns'; diff --git a/yarn.lock b/yarn.lock index 837c77f15..bb1bb713e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8994,10 +8994,30 @@ __metadata: languageName: unknown linkType: soft +"@giantswarm/backstage-plugin-catalog-backend-module-gs@npm:^0.1.0, @giantswarm/backstage-plugin-catalog-backend-module-gs@workspace:plugins/catalog-backend-module-gs": + version: 0.0.0-use.local + resolution: "@giantswarm/backstage-plugin-catalog-backend-module-gs@workspace:plugins/catalog-backend-module-gs" + dependencies: + "@backstage/backend-plugin-api": "backstage:^" + "@backstage/backend-test-utils": "backstage:^" + "@backstage/catalog-model": "backstage:^" + "@backstage/cli": "backstage:^" + "@backstage/config": "backstage:^" + "@backstage/integration": "backstage:^" + "@backstage/plugin-catalog-common": "backstage:^" + "@backstage/plugin-catalog-node": "backstage:^" + "@giantswarm/backstage-plugin-gs-common": "npm:^0.12.0" + "@octokit/graphql": "npm:^8.2.1" + "@types/lodash": "npm:^4.17.5" + lodash: "npm:^4.17.21" + languageName: unknown + linkType: soft + "@giantswarm/backstage-plugin-gs-common@npm:^0.12.0, @giantswarm/backstage-plugin-gs-common@workspace:plugins/gs-common": version: 0.0.0-use.local resolution: "@giantswarm/backstage-plugin-gs-common@workspace:plugins/gs-common" dependencies: + "@backstage/catalog-model": "npm:^1.7.3" "@backstage/cli": "backstage:^" "@types/js-yaml": "npm:^4" "@types/lodash": "npm:^4.17.5" @@ -11111,6 +11131,16 @@ __metadata: languageName: node linkType: hard +"@octokit/endpoint@npm:^10.1.3": + version: 10.1.3 + resolution: "@octokit/endpoint@npm:10.1.3" + dependencies: + "@octokit/types": "npm:^13.6.2" + universal-user-agent: "npm:^7.0.2" + checksum: 10c0/096956534efee1f683b4749673c2d1673c6fbe5362b9cce553f9f4b956feaf59bde816594de72f4352f749b862d0b15bc0e2fa7fb0e198deb1fe637b5f4a8bc7 + languageName: node + linkType: hard + "@octokit/endpoint@npm:^7.0.0": version: 7.0.6 resolution: "@octokit/endpoint@npm:7.0.6" @@ -11185,6 +11215,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^8.2.1": + version: 8.2.1 + resolution: "@octokit/graphql@npm:8.2.1" + dependencies: + "@octokit/request": "npm:^9.2.2" + "@octokit/types": "npm:^13.8.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/79fe7b50113bef90a32e3b6ee48923cad2afc049aba5c22e44167cf5773e2688a4e953f3ee1e24bee9706ccf7588ae14451933b282f63f1f7d5c95d319df23dd + languageName: node + linkType: hard + "@octokit/oauth-app@npm:^4.2.0": version: 4.2.4 resolution: "@octokit/oauth-app@npm:4.2.4" @@ -11286,6 +11327,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^24.2.0": + version: 24.2.0 + resolution: "@octokit/openapi-types@npm:24.2.0" + checksum: 10c0/8f47918b35e9b7f6109be6f7c8fc3a64ad13a48233112b29e92559e64a564b810eb3ebf81b4cd0af1bb2989d27b9b95cca96e841ec4e23a3f68703cefe62fd9e + languageName: node + linkType: hard + "@octokit/plugin-paginate-graphql@npm:^4.0.0": version: 4.0.1 resolution: "@octokit/plugin-paginate-graphql@npm:4.0.1" @@ -11418,6 +11466,15 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^6.1.7": + version: 6.1.7 + resolution: "@octokit/request-error@npm:6.1.7" + dependencies: + "@octokit/types": "npm:^13.6.2" + checksum: 10c0/24bd6f98b1d7b2d4062de34777b4195d3cc4dc40c3187a0321dd588291ec5e13b5760765aacdef3a73796a529d3dec0bfb820780be6ef526a3e774d13566b5b0 + languageName: node + linkType: hard + "@octokit/request@npm:^6.0.0, @octokit/request@npm:^6.2.3": version: 6.2.8 resolution: "@octokit/request@npm:6.2.8" @@ -11456,6 +11513,19 @@ __metadata: languageName: node linkType: hard +"@octokit/request@npm:^9.2.2": + version: 9.2.2 + resolution: "@octokit/request@npm:9.2.2" + dependencies: + "@octokit/endpoint": "npm:^10.1.3" + "@octokit/request-error": "npm:^6.1.7" + "@octokit/types": "npm:^13.6.2" + fast-content-type-parse: "npm:^2.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10c0/14cb523c17ed619c63e52025af9fdc67357b63d113905ec0ccb47badd20926e6f37a17a0620d3a906823b496e3b7efb29ed1e2af658cde5daf3ed3f88b421973 + languageName: node + linkType: hard + "@octokit/rest@npm:^19.0.3": version: 19.0.13 resolution: "@octokit/rest@npm:19.0.13" @@ -11502,6 +11572,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^13.6.2, @octokit/types@npm:^13.8.0": + version: 13.10.0 + resolution: "@octokit/types@npm:13.10.0" + dependencies: + "@octokit/openapi-types": "npm:^24.2.0" + checksum: 10c0/f66a401b89d653ec28e5c1529abdb7965752db4d9d40fa54c80e900af4c6bf944af6bd0a83f5b4f1eecb72e3d646899dfb27ffcf272ac243552de7e3b97a038d + languageName: node + linkType: hard + "@octokit/types@npm:^8.0.0": version: 8.2.1 resolution: "@octokit/types@npm:8.2.1" @@ -16295,6 +16374,7 @@ __metadata: "@backstage/plugin-search-backend-node": "backstage:^" "@backstage/plugin-techdocs-backend": "backstage:^" "@giantswarm/backstage-plugin-auth-backend-module-gs": "npm:^0.8.0" + "@giantswarm/backstage-plugin-catalog-backend-module-gs": "npm:^0.1.0" "@giantswarm/backstage-plugin-scaffolder-backend-module-gs": "npm:^0.7.0" "@giantswarm/backstage-plugin-techdocs-backend-module-gs": "npm:^0.7.0" app: "link:../app" @@ -20612,6 +20692,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^2.0.0": + version: 2.0.1 + resolution: "fast-content-type-parse@npm:2.0.1" + checksum: 10c0/e5ff87d75a35ae4cf377df1dca46ec49e7abbdc8513689676ecdef548b94900b50e66e516e64470035d79b9f7010ef15d98c24d8ae803a881363cc59e0715e19 + languageName: node + linkType: hard + "fast-copy@npm:^3.0.2": version: 3.0.2 resolution: "fast-copy@npm:3.0.2" @@ -33318,6 +33405,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "universal-user-agent@npm:7.0.2" + checksum: 10c0/e60517ee929813e6b3ac0ceb3c66deccafadc71341edca160279ff046319c684fd7090a60d63aa61cd34a06c2d2acebeb8c2f8d364244ae7bf8ab788e20cd8c8 + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2"