@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot 0.2.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 ADDED
@@ -0,0 +1,18 @@
1
+ # @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 526b9f3: added dependabot scorecard
8
+ - d706601: Backstage version bump to v1.49.3
9
+
10
+ ### Patch Changes
11
+
12
+ - 1657da3: Added github.com/dependabot annotation
13
+ - Updated dependencies [d706601]
14
+ - Updated dependencies [55226c2]
15
+ - Updated dependencies [243ad0a]
16
+ - Updated dependencies [c83b206]
17
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@2.5.0
18
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@2.5.0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Scorecard Backend Module: Dependabot
2
+
3
+ Adds Dependabot alerts as a scorecard metric (`dependabot.alerts`, 0–9 from severity).
4
+
5
+ **Install:** `yarn workspace backend add @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot` then `backend.add(import('@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot'))`.
6
+
7
+ **Setup:** Entities need `github.com/project-slug: owner/repo` and `github.com/dependabot: 'true'` (exact string) to opt in. GitHub token must have `security_events` (or Dependabot read) so the backend can call the Dependabot API.
8
+
9
+ **How it works:** **DependabotClient** fetches open alerts from the GitHub API (by severity, with pagination). **DependabotMetricProvider** (one per severity) uses the client to score entities. The **factory** (`createDependabotMetricProvider` / `createDependabotMetricProviders`) builds single or all-four providers; the module registers the four (critical, high, medium, low) with the scorecard backend.
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var integration = require('@backstage/integration');
4
+ var rest = require('@octokit/rest');
5
+
6
+ const PER_PAGE = 100;
7
+ class DependabotClient {
8
+ integrations;
9
+ logger;
10
+ constructor(config, logger) {
11
+ this.integrations = integration.ScmIntegrations.fromConfig(config);
12
+ this.logger = logger.child({ component: "DependabotClient" });
13
+ }
14
+ async getOctokit(url) {
15
+ const githubIntegration = this.integrations.github.byUrl(url);
16
+ if (!githubIntegration) {
17
+ throw new Error(`Missing GitHub integration for '${url}'`);
18
+ }
19
+ const baseUrl = githubIntegration.config.apiBaseUrl;
20
+ if (!baseUrl) {
21
+ throw new Error(`Missing GitHub API base URL for '${url}'`);
22
+ }
23
+ const credentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
24
+ const { token } = await credentialsProvider.getCredentials({
25
+ url
26
+ });
27
+ if (!token) {
28
+ throw new Error(`Missing GitHub token for '${url}'`);
29
+ }
30
+ return new rest.Octokit({
31
+ auth: token,
32
+ baseUrl: baseUrl.replace(/\/$/, "")
33
+ });
34
+ }
35
+ /**
36
+ * @param url - The URL of the repository.
37
+ * @param repository - The repository owner and name.
38
+ * @param severity - The severity of the alerts to fetch.
39
+ * @returns All alerts for the given repository and severity.
40
+ */
41
+ async getAlerts(url, repository, severity) {
42
+ this.logger.debug(
43
+ `Fetching Dependabot ${severity} alerts for ${repository.owner}/${repository.repo}`
44
+ );
45
+ try {
46
+ const octokit = await this.getOctokit(url);
47
+ const allAlerts = await octokit.paginate(
48
+ "GET /repos/{owner}/{repo}/dependabot/alerts",
49
+ {
50
+ owner: repository.owner,
51
+ repo: repository.repo,
52
+ state: "open",
53
+ severity,
54
+ per_page: PER_PAGE
55
+ }
56
+ );
57
+ this.logger.debug(
58
+ `Fetched ${allAlerts.length} Dependabot ${severity} alert(s) for ${repository.owner}/${repository.repo}`
59
+ );
60
+ return allAlerts;
61
+ } catch (error) {
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ this.logger.warn(
64
+ `Failed to fetch Dependabot ${severity} alerts for ${repository.owner}/${repository.repo}: ${message}`,
65
+ error
66
+ );
67
+ throw error;
68
+ }
69
+ }
70
+ }
71
+
72
+ exports.DependabotClient = DependabotClient;
73
+ //# sourceMappingURL=DependabotClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependabotClient.cjs.js","sources":["../../src/clients/DependabotClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { LoggerService } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport {\n DefaultGithubCredentialsProvider,\n ScmIntegrations,\n} from '@backstage/integration';\nimport { Octokit } from '@octokit/rest';\nimport {\n DependabotRepository,\n DependabotSeverity,\n} from '../metricProviders/DependabotConfig';\n\ninterface GitHubDependabotAlert {\n number: number;\n description: string;\n createdAt: string;\n state: string;\n securityAdvisory: {\n severity: string;\n };\n}\n\nconst PER_PAGE = 100;\nexport class DependabotClient {\n private readonly integrations: ScmIntegrations;\n private readonly logger: LoggerService;\n\n constructor(config: Config, logger: LoggerService) {\n this.integrations = ScmIntegrations.fromConfig(config);\n this.logger = logger.child({ component: 'DependabotClient' });\n }\n\n private async getOctokit(url: string): Promise<Octokit> {\n const githubIntegration = this.integrations.github.byUrl(url);\n if (!githubIntegration) {\n throw new Error(`Missing GitHub integration for '${url}'`);\n }\n\n const baseUrl = githubIntegration.config.apiBaseUrl;\n if (!baseUrl) {\n throw new Error(`Missing GitHub API base URL for '${url}'`);\n }\n\n const credentialsProvider =\n DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);\n\n const { token } = await credentialsProvider.getCredentials({\n url,\n });\n\n if (!token) {\n throw new Error(`Missing GitHub token for '${url}'`);\n }\n\n return new Octokit({\n auth: token,\n baseUrl: baseUrl.replace(/\\/$/, ''),\n });\n }\n /**\n * @param url - The URL of the repository.\n * @param repository - The repository owner and name.\n * @param severity - The severity of the alerts to fetch.\n * @returns All alerts for the given repository and severity.\n */\n async getAlerts(\n url: string,\n repository: DependabotRepository,\n severity: DependabotSeverity,\n ): Promise<GitHubDependabotAlert[]> {\n this.logger.debug(\n `Fetching Dependabot ${severity} alerts for ${repository.owner}/${repository.repo}`,\n );\n try {\n const octokit = await this.getOctokit(url);\n\n const allAlerts = (await octokit.paginate(\n 'GET /repos/{owner}/{repo}/dependabot/alerts',\n {\n owner: repository.owner,\n repo: repository.repo,\n state: 'open',\n severity,\n per_page: PER_PAGE,\n },\n )) as unknown as GitHubDependabotAlert[];\n\n this.logger.debug(\n `Fetched ${allAlerts.length} Dependabot ${severity} alert(s) for ${repository.owner}/${repository.repo}`,\n );\n return allAlerts;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.warn(\n `Failed to fetch Dependabot ${severity} alerts for ${repository.owner}/${repository.repo}: ${message}`,\n error,\n );\n throw error;\n }\n }\n}\n"],"names":["ScmIntegrations","DefaultGithubCredentialsProvider","Octokit"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var module$1 = require('./module.cjs.js');
6
+
7
+
8
+
9
+ exports.default = module$1.scorecardModuleDependabot;
10
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -0,0 +1,5 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+
3
+ declare const scorecardModuleDependabot: _backstage_backend_plugin_api.BackendFeature;
4
+
5
+ export { scorecardModuleDependabot as default };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const DEPENDABOT_SEVERITIES = [
4
+ "critical",
5
+ "high",
6
+ "medium",
7
+ "low"
8
+ ];
9
+ const DEPENDABOT_THRESHOLDS = {
10
+ rules: [
11
+ { key: "success", expression: "<1" },
12
+ { key: "warning", expression: "1-7" },
13
+ { key: "error", expression: ">7" }
14
+ ]
15
+ };
16
+ const DEPENDABOT_SEVERITY_METRIC = {
17
+ critical: {
18
+ id: "dependabot.alerts_critical",
19
+ title: "Dependabot Critical Alerts",
20
+ description: "Current count of open critical Dependabot alerts for a given repository."
21
+ },
22
+ high: {
23
+ id: "dependabot.alerts_high",
24
+ title: "Dependabot High Alerts",
25
+ description: "Current count of open high-severity Dependabot alerts for a given repository."
26
+ },
27
+ medium: {
28
+ id: "dependabot.alerts_medium",
29
+ title: "Dependabot Medium Alerts",
30
+ description: "Current count of open medium-severity Dependabot alerts for a given repository."
31
+ },
32
+ low: {
33
+ id: "dependabot.alerts_low",
34
+ title: "Dependabot Low Alerts",
35
+ description: "Current count of open low-severity Dependabot alerts for a given repository."
36
+ }
37
+ };
38
+
39
+ exports.DEPENDABOT_SEVERITIES = DEPENDABOT_SEVERITIES;
40
+ exports.DEPENDABOT_SEVERITY_METRIC = DEPENDABOT_SEVERITY_METRIC;
41
+ exports.DEPENDABOT_THRESHOLDS = DEPENDABOT_THRESHOLDS;
42
+ //# sourceMappingURL=DependabotConfig.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependabotConfig.cjs.js","sources":["../../src/metricProviders/DependabotConfig.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 { ThresholdConfig } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\n\nexport const DEPENDABOT_SEVERITIES = [\n 'critical',\n 'high',\n 'medium',\n 'low',\n] as const;\n\nexport type DependabotSeverity = (typeof DEPENDABOT_SEVERITIES)[number];\n\nexport type DependabotRepository = {\n owner: string;\n repo: string;\n};\n\nexport interface DependabotMetricConfig {\n name: string;\n displayTitle: string;\n description: string;\n}\n\nexport const DEPENDABOT_THRESHOLDS: ThresholdConfig = {\n rules: [\n { key: 'success', expression: '<1' },\n { key: 'warning', expression: '1-7' },\n { key: 'error', expression: '>7' },\n ],\n};\n\nexport const DEPENDABOT_SEVERITY_METRIC: Record<\n DependabotSeverity,\n { id: string; title: string; description: string }\n> = {\n critical: {\n id: 'dependabot.alerts_critical',\n title: 'Dependabot Critical Alerts',\n description:\n 'Current count of open critical Dependabot alerts for a given repository.',\n },\n high: {\n id: 'dependabot.alerts_high',\n title: 'Dependabot High Alerts',\n description:\n 'Current count of open high-severity Dependabot alerts for a given repository.',\n },\n medium: {\n id: 'dependabot.alerts_medium',\n title: 'Dependabot Medium Alerts',\n description:\n 'Current count of open medium-severity Dependabot alerts for a given repository.',\n },\n low: {\n id: 'dependabot.alerts_low',\n title: 'Dependabot Low Alerts',\n description:\n 'Current count of open low-severity Dependabot alerts for a given repository.',\n },\n};\n"],"names":[],"mappings":";;AAkBO,MAAM,qBAAwB,GAAA;AAAA,EACnC,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF;AAeO,MAAM,qBAAyC,GAAA;AAAA,EACpD,KAAO,EAAA;AAAA,IACL,EAAE,GAAA,EAAK,SAAW,EAAA,UAAA,EAAY,IAAK,EAAA;AAAA,IACnC,EAAE,GAAA,EAAK,SAAW,EAAA,UAAA,EAAY,KAAM,EAAA;AAAA,IACpC,EAAE,GAAA,EAAK,OAAS,EAAA,UAAA,EAAY,IAAK;AAAA;AAErC;AAEO,MAAM,0BAGT,GAAA;AAAA,EACF,QAAU,EAAA;AAAA,IACR,EAAI,EAAA,4BAAA;AAAA,IACJ,KAAO,EAAA,4BAAA;AAAA,IACP,WACE,EAAA;AAAA,GACJ;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,EAAI,EAAA,wBAAA;AAAA,IACJ,KAAO,EAAA,wBAAA;AAAA,IACP,WACE,EAAA;AAAA,GACJ;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,EAAI,EAAA,0BAAA;AAAA,IACJ,KAAO,EAAA,0BAAA;AAAA,IACP,WACE,EAAA;AAAA,GACJ;AAAA,EACA,GAAK,EAAA;AAAA,IACH,EAAI,EAAA,uBAAA;AAAA,IACJ,KAAO,EAAA,uBAAA;AAAA,IACP,WACE,EAAA;AAAA;AAEN;;;;;;"}
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+ var catalogClient = require('@backstage/catalog-client');
5
+ var DependabotClient = require('../clients/DependabotClient.cjs.js');
6
+ var DependabotConfig = require('./DependabotConfig.cjs.js');
7
+
8
+ const GITHUB_PROJECT_ANNOTATION = "github.com/project-slug";
9
+ class DependabotMetricProvider {
10
+ dependabotClient;
11
+ thresholds;
12
+ severity;
13
+ constructor(config, logger, severity, thresholds) {
14
+ this.severity = severity;
15
+ this.dependabotClient = new DependabotClient.DependabotClient(config, logger);
16
+ this.thresholds = thresholds ?? DependabotConfig.DEPENDABOT_THRESHOLDS;
17
+ }
18
+ getProviderDatasourceId() {
19
+ return "dependabot";
20
+ }
21
+ getProviderId() {
22
+ return DependabotConfig.DEPENDABOT_SEVERITY_METRIC[this.severity].id;
23
+ }
24
+ getMetricType() {
25
+ return "number";
26
+ }
27
+ getMetric() {
28
+ const meta = DependabotConfig.DEPENDABOT_SEVERITY_METRIC[this.severity];
29
+ return {
30
+ id: meta.id,
31
+ title: meta.title,
32
+ description: meta.description,
33
+ type: this.getMetricType(),
34
+ history: true
35
+ };
36
+ }
37
+ getMetricThresholds() {
38
+ return this.thresholds;
39
+ }
40
+ getCatalogFilter() {
41
+ return {
42
+ "metadata.annotations.github.com/project-slug": catalogClient.CATALOG_FILTER_EXISTS,
43
+ "metadata.annotations.github.com/dependabot": "true"
44
+ };
45
+ }
46
+ getRepository(entity) {
47
+ const projectSlug = entity.metadata.annotations?.[GITHUB_PROJECT_ANNOTATION];
48
+ if (!projectSlug) {
49
+ throw new Error(
50
+ `Missing annotation '${GITHUB_PROJECT_ANNOTATION}' for entity ${catalogModel.stringifyEntityRef(
51
+ entity
52
+ )}`
53
+ );
54
+ }
55
+ const [owner, repo] = projectSlug.split("/");
56
+ if (!owner || !repo) {
57
+ throw new Error(
58
+ `Invalid format of '${GITHUB_PROJECT_ANNOTATION}' ${projectSlug} for entity ${catalogModel.stringifyEntityRef(
59
+ entity
60
+ )}`
61
+ );
62
+ }
63
+ return { owner, repo };
64
+ }
65
+ async calculateMetric(entity) {
66
+ const repository = this.getRepository(entity);
67
+ const { target } = catalogModel.getEntitySourceLocation(entity);
68
+ const alerts = await this.dependabotClient.getAlerts(
69
+ target,
70
+ repository,
71
+ this.severity
72
+ );
73
+ return alerts.length;
74
+ }
75
+ }
76
+
77
+ exports.DependabotMetricProvider = DependabotMetricProvider;
78
+ //# sourceMappingURL=DependabotMetricProvider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependabotMetricProvider.cjs.js","sources":["../../src/metricProviders/DependabotMetricProvider.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 {\n Metric,\n ThresholdConfig,\n} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport {\n stringifyEntityRef,\n type Entity,\n getEntitySourceLocation,\n} from '@backstage/catalog-model';\nimport { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';\n\nimport { DependabotClient } from '../clients/DependabotClient';\nimport {\n type DependabotSeverity,\n DEPENDABOT_SEVERITY_METRIC,\n DEPENDABOT_THRESHOLDS,\n DependabotRepository,\n} from './DependabotConfig';\n\nconst GITHUB_PROJECT_ANNOTATION = 'github.com/project-slug';\n\n/**\n * Metric provider for Dependabot alerts of a single severity (critical, high, medium, low).\n * Use one instance per severity; the module registers four providers.\n */\nexport class DependabotMetricProvider implements MetricProvider<'number'> {\n private readonly dependabotClient: DependabotClient;\n private readonly thresholds: ThresholdConfig;\n private readonly severity: DependabotSeverity;\n\n constructor(\n config: Config,\n logger: LoggerService,\n severity: DependabotSeverity,\n thresholds?: ThresholdConfig,\n ) {\n this.severity = severity;\n this.dependabotClient = new DependabotClient(config, logger);\n this.thresholds = thresholds ?? DEPENDABOT_THRESHOLDS;\n }\n\n getProviderDatasourceId(): string {\n return 'dependabot';\n }\n\n getProviderId(): string {\n return DEPENDABOT_SEVERITY_METRIC[this.severity].id;\n }\n\n getMetricType(): 'number' {\n return 'number';\n }\n\n getMetric(): Metric<'number'> {\n const meta = DEPENDABOT_SEVERITY_METRIC[this.severity];\n return {\n id: meta.id,\n title: meta.title,\n description: meta.description,\n type: this.getMetricType(),\n history: true,\n };\n }\n\n getMetricThresholds(): ThresholdConfig {\n return this.thresholds;\n }\n\n getCatalogFilter(): Record<string, string | symbol | (string | symbol)[]> {\n return {\n 'metadata.annotations.github.com/project-slug': CATALOG_FILTER_EXISTS,\n 'metadata.annotations.github.com/dependabot': 'true',\n };\n }\n\n getRepository(entity: Entity): DependabotRepository {\n const projectSlug =\n entity.metadata.annotations?.[GITHUB_PROJECT_ANNOTATION];\n if (!projectSlug) {\n throw new Error(\n `Missing annotation '${GITHUB_PROJECT_ANNOTATION}' for entity ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n\n const [owner, repo] = projectSlug.split('/');\n if (!owner || !repo) {\n throw new Error(\n `Invalid format of '${GITHUB_PROJECT_ANNOTATION}' ${projectSlug} for entity ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n\n return { owner, repo };\n }\n\n async calculateMetric(entity: Entity): Promise<number> {\n const repository = this.getRepository(entity);\n const { target } = getEntitySourceLocation(entity);\n const alerts = await this.dependabotClient.getAlerts(\n target,\n repository,\n this.severity,\n );\n return alerts.length;\n }\n}\n"],"names":["DependabotClient","DEPENDABOT_THRESHOLDS","DEPENDABOT_SEVERITY_METRIC","CATALOG_FILTER_EXISTS","stringifyEntityRef","getEntitySourceLocation"],"mappings":";;;;;;;AAsCA,MAAM,yBAA4B,GAAA,yBAAA;AAM3B,MAAM,wBAA6D,CAAA;AAAA,EACvD,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EAEjB,WACE,CAAA,MAAA,EACA,MACA,EAAA,QAAA,EACA,UACA,EAAA;AACA,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA;AAChB,IAAA,IAAA,CAAK,gBAAmB,GAAA,IAAIA,iCAAiB,CAAA,MAAA,EAAQ,MAAM,CAAA;AAC3D,IAAA,IAAA,CAAK,aAAa,UAAc,IAAAC,sCAAA;AAAA;AAClC,EAEA,uBAAkC,GAAA;AAChC,IAAO,OAAA,YAAA;AAAA;AACT,EAEA,aAAwB,GAAA;AACtB,IAAO,OAAAC,2CAAA,CAA2B,IAAK,CAAA,QAAQ,CAAE,CAAA,EAAA;AAAA;AACnD,EAEA,aAA0B,GAAA;AACxB,IAAO,OAAA,QAAA;AAAA;AACT,EAEA,SAA8B,GAAA;AAC5B,IAAM,MAAA,IAAA,GAAOA,2CAA2B,CAAA,IAAA,CAAK,QAAQ,CAAA;AACrD,IAAO,OAAA;AAAA,MACL,IAAI,IAAK,CAAA,EAAA;AAAA,MACT,OAAO,IAAK,CAAA,KAAA;AAAA,MACZ,aAAa,IAAK,CAAA,WAAA;AAAA,MAClB,IAAA,EAAM,KAAK,aAAc,EAAA;AAAA,MACzB,OAAS,EAAA;AAAA,KACX;AAAA;AACF,EAEA,mBAAuC,GAAA;AACrC,IAAA,OAAO,IAAK,CAAA,UAAA;AAAA;AACd,EAEA,gBAA0E,GAAA;AACxE,IAAO,OAAA;AAAA,MACL,8CAAgD,EAAAC,mCAAA;AAAA,MAChD,4CAA8C,EAAA;AAAA,KAChD;AAAA;AACF,EAEA,cAAc,MAAsC,EAAA;AAClD,IAAA,MAAM,WACJ,GAAA,MAAA,CAAO,QAAS,CAAA,WAAA,GAAc,yBAAyB,CAAA;AACzD,IAAA,IAAI,CAAC,WAAa,EAAA;AAChB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,oBAAA,EAAuB,yBAAyB,CAAgB,aAAA,EAAAC,+BAAA;AAAA,UAC9D;AAAA,SACD,CAAA;AAAA,OACH;AAAA;AAGF,IAAA,MAAM,CAAC,KAAO,EAAA,IAAI,CAAI,GAAA,WAAA,CAAY,MAAM,GAAG,CAAA;AAC3C,IAAI,IAAA,CAAC,KAAS,IAAA,CAAC,IAAM,EAAA;AACnB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAsB,mBAAA,EAAA,yBAAyB,CAAK,EAAA,EAAA,WAAW,CAAe,YAAA,EAAAA,+BAAA;AAAA,UAC5E;AAAA,SACD,CAAA;AAAA,OACH;AAAA;AAGF,IAAO,OAAA,EAAE,OAAO,IAAK,EAAA;AAAA;AACvB,EAEA,MAAM,gBAAgB,MAAiC,EAAA;AACrD,IAAM,MAAA,UAAA,GAAa,IAAK,CAAA,aAAA,CAAc,MAAM,CAAA;AAC5C,IAAA,MAAM,EAAE,MAAA,EAAW,GAAAC,oCAAA,CAAwB,MAAM,CAAA;AACjD,IAAM,MAAA,MAAA,GAAS,MAAM,IAAA,CAAK,gBAAiB,CAAA,SAAA;AAAA,MACzC,MAAA;AAAA,MACA,UAAA;AAAA,MACA,IAAK,CAAA;AAAA,KACP;AACA,IAAA,OAAO,MAAO,CAAA,MAAA;AAAA;AAElB;;;;"}
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var DependabotMetricProvider = require('./DependabotMetricProvider.cjs.js');
4
+ var DependabotConfig = require('./DependabotConfig.cjs.js');
5
+
6
+ function createDependabotMetricProvider(config, logger, severity, thresholds) {
7
+ return new DependabotMetricProvider.DependabotMetricProvider(config, logger, severity, thresholds);
8
+ }
9
+ function createDependabotMetricProviders(config, logger, thresholds) {
10
+ return DependabotConfig.DEPENDABOT_SEVERITIES.map(
11
+ (severity) => createDependabotMetricProvider(config, logger, severity, thresholds)
12
+ );
13
+ }
14
+
15
+ exports.createDependabotMetricProvider = createDependabotMetricProvider;
16
+ exports.createDependabotMetricProviders = createDependabotMetricProviders;
17
+ //# sourceMappingURL=DependabotMetricProviderFactory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependabotMetricProviderFactory.cjs.js","sources":["../../src/metricProviders/DependabotMetricProviderFactory.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\nimport { DependabotMetricProvider } from './DependabotMetricProvider';\nimport { DependabotSeverity, DEPENDABOT_SEVERITIES } from './DependabotConfig';\nimport { Config } from '@backstage/config';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ThresholdConfig } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';\nimport { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\n\n/**\n * Creates a single Dependabot metric provider for the given severity.\n */\nexport function createDependabotMetricProvider(\n config: Config,\n logger: LoggerService,\n severity: DependabotSeverity,\n thresholds?: ThresholdConfig,\n): MetricProvider<'number'> {\n return new DependabotMetricProvider(config, logger, severity, thresholds);\n}\n\n/**\n * Creates one metric provider per severity (critical, high, medium, low).\n */\nexport function createDependabotMetricProviders(\n config: Config,\n logger: LoggerService,\n thresholds?: ThresholdConfig,\n): MetricProvider<'number'>[] {\n return DEPENDABOT_SEVERITIES.map(severity =>\n createDependabotMetricProvider(config, logger, severity, thresholds),\n );\n}\n"],"names":["DependabotMetricProvider","DEPENDABOT_SEVERITIES"],"mappings":";;;;;AAyBO,SAAS,8BACd,CAAA,MAAA,EACA,MACA,EAAA,QAAA,EACA,UAC0B,EAAA;AAC1B,EAAA,OAAO,IAAIA,iDAAA,CAAyB,MAAQ,EAAA,MAAA,EAAQ,UAAU,UAAU,CAAA;AAC1E;AAKgB,SAAA,+BAAA,CACd,MACA,EAAA,MAAA,EACA,UAC4B,EAAA;AAC5B,EAAA,OAAOC,sCAAsB,CAAA,GAAA;AAAA,IAAI,CAC/B,QAAA,KAAA,8BAAA,CAA+B,MAAQ,EAAA,MAAA,EAAQ,UAAU,UAAU;AAAA,GACrE;AACF;;;;;"}
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var backstagePluginScorecardNode = require('@red-hat-developer-hub/backstage-plugin-scorecard-node');
5
+ var DependabotMetricProviderFactory = require('./metricProviders/DependabotMetricProviderFactory.cjs.js');
6
+
7
+ const scorecardModuleDependabot = backendPluginApi.createBackendModule({
8
+ pluginId: "scorecard",
9
+ moduleId: "dependabot",
10
+ register(reg) {
11
+ reg.registerInit({
12
+ deps: {
13
+ metrics: backstagePluginScorecardNode.scorecardMetricsExtensionPoint,
14
+ config: backendPluginApi.coreServices.rootConfig,
15
+ logger: backendPluginApi.coreServices.logger
16
+ },
17
+ async init({ metrics, config, logger }) {
18
+ const providers = DependabotMetricProviderFactory.createDependabotMetricProviders(config, logger);
19
+ for (const provider of providers) {
20
+ metrics.addMetricProvider(provider);
21
+ }
22
+ }
23
+ });
24
+ }
25
+ });
26
+
27
+ exports.scorecardModuleDependabot = scorecardModuleDependabot;
28
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { scorecardMetricsExtensionPoint } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';\nimport { createDependabotMetricProviders } from './metricProviders/DependabotMetricProviderFactory';\n\nexport const scorecardModuleDependabot = createBackendModule({\n pluginId: 'scorecard',\n moduleId: 'dependabot',\n register(reg) {\n reg.registerInit({\n deps: {\n metrics: scorecardMetricsExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n\n async init({ metrics, config, logger }) {\n const providers = createDependabotMetricProviders(config, logger);\n for (const provider of providers) {\n metrics.addMetricProvider(provider);\n }\n },\n });\n },\n});\n"],"names":["createBackendModule","scorecardMetricsExtensionPoint","coreServices","createDependabotMetricProviders"],"mappings":";;;;;;AAsBO,MAAM,4BAA4BA,oCAAoB,CAAA;AAAA,EAC3D,QAAU,EAAA,WAAA;AAAA,EACV,QAAU,EAAA,YAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,OAAS,EAAAC,2DAAA;AAAA,QACT,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA;AAAA,OACvB;AAAA,MAEA,MAAM,IAAK,CAAA,EAAE,OAAS,EAAA,MAAA,EAAQ,QAAU,EAAA;AACtC,QAAM,MAAA,SAAA,GAAYC,+DAAgC,CAAA,MAAA,EAAQ,MAAM,CAAA;AAChE,QAAA,KAAA,MAAW,YAAY,SAAW,EAAA;AAChC,UAAA,OAAA,CAAQ,kBAAkB,QAAQ,CAAA;AAAA;AACpC;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot",
3
+ "version": "0.2.0",
4
+ "license": "Apache-2.0",
5
+ "description": "The dependabot backend module for the scorecard plugin.",
6
+ "main": "dist/index.cjs.js",
7
+ "types": "dist/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "main": "dist/index.cjs.js",
11
+ "types": "dist/index.d.ts"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/redhat-developer/rhdh-plugins",
16
+ "directory": "workspaces/scorecard/plugins/scorecard-backend-module-dependabot"
17
+ },
18
+ "backstage": {
19
+ "role": "backend-plugin-module",
20
+ "pluginId": "scorecard",
21
+ "pluginPackage": "@red-hat-developer-hub/backstage-plugin-scorecard-backend",
22
+ "features": {
23
+ ".": "@backstage/BackendFeature"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "start": "backstage-cli package start",
28
+ "build": "backstage-cli package build",
29
+ "lint": "backstage-cli package lint",
30
+ "test": "backstage-cli package test",
31
+ "clean": "backstage-cli package clean",
32
+ "prepack": "backstage-cli package prepack",
33
+ "postpack": "backstage-cli package postpack"
34
+ },
35
+ "dependencies": {
36
+ "@backstage/backend-plugin-api": "^1.8.0",
37
+ "@backstage/catalog-client": "^1.14.0",
38
+ "@backstage/catalog-model": "^1.7.7",
39
+ "@backstage/config": "^1.3.6",
40
+ "@backstage/integration": "^2.0.0",
41
+ "@octokit/rest": "^19.0.3",
42
+ "@red-hat-developer-hub/backstage-plugin-scorecard-common": "^2.5.0",
43
+ "@red-hat-developer-hub/backstage-plugin-scorecard-node": "^2.5.0"
44
+ },
45
+ "devDependencies": {
46
+ "@backstage/backend-test-utils": "^1.11.1",
47
+ "@backstage/cli": "^0.36.0"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "typesVersions": {
53
+ "*": {
54
+ "package.json": [
55
+ "package.json"
56
+ ]
57
+ }
58
+ }
59
+ }