@evergreen-dev/backstage-plugin-backend 0.0.0-stage → 0.1.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/config.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export interface Config {
2
+ /**
3
+ * Evergreen developer-portal integration.
4
+ * The backend only reads a published `evergreen.portal.v1` export; it never scans.
5
+ */
6
+ evergreen?: {
7
+ /**
8
+ * HTTP(S) URL or absolute filesystem path of the portal export JSON
9
+ * produced by `evergreen export --format portal`.
10
+ */
11
+ portalExportSource: string;
12
+ };
13
+ }
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@evergreen-dev/backstage-plugin-backend",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Backstage backend module that serves Evergreen portal-export lifecycle summaries. Does not scan repositories.",
5
+ "license": "Apache-2.0",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "backstage": {
9
+ "role": "backend-plugin",
10
+ "pluginId": "evergreen",
11
+ "pluginPackages": [
12
+ "@evergreen-dev/backstage-plugin",
13
+ "@evergreen-dev/backstage-plugin-backend",
14
+ "@evergreen-dev/backstage-plugin-common"
15
+ ]
16
+ },
17
+ "scripts": {
18
+ "start": "backstage-cli package start",
19
+ "build": "backstage-cli package build",
20
+ "lint": "backstage-cli package lint",
21
+ "test": "backstage-cli package test",
22
+ "clean": "backstage-cli package clean"
23
+ },
24
+ "dependencies": {
25
+ "@evergreen-dev/backstage-plugin-common": "^0.1.0"
26
+ },
27
+ "peerDependencies": {
28
+ "@backstage/backend-plugin-api": "^1.0.0",
29
+ "@backstage/config": "^1.0.0",
30
+ "@backstage/errors": "^1.0.0",
31
+ "express": "^4.18.0 || ^5.0.0",
32
+ "express-promise-router": "^4.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "@backstage/backend-plugin-api": "^1.0.0",
36
+ "@backstage/cli": "^0.30.0",
37
+ "@backstage/config": "^1.0.0",
38
+ "@backstage/errors": "^1.0.0",
39
+ "@types/express": "^4.17.21",
40
+ "express": "^4.21.0",
41
+ "express-promise-router": "^4.1.1"
42
+ },
43
+ "files": [
44
+ "src",
45
+ "config.d.ts"
46
+ ]
47
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { evergreenPlugin as default } from "./plugin";
2
+ export { createRouter } from "./router";
3
+ export { loadPortalCatalog } from "./loadPortal";
4
+ export {
5
+ PORTAL_SCHEMA_VERSION,
6
+ SERVICE_PATH_ANNOTATION,
7
+ findService,
8
+ parsePortalCatalog,
9
+ } from "@evergreen-dev/backstage-plugin-common";
@@ -0,0 +1,33 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { pathToFileURL } from "node:url";
3
+
4
+ import { parsePortalCatalog, type PortalCatalog } from "@evergreen-dev/backstage-plugin-common";
5
+
6
+ /**
7
+ * Load an `evergreen.portal.v1` catalog from a URL or a local file path
8
+ * (CI artifact on disk, object-store URL, or Pages URL).
9
+ */
10
+ export async function loadPortalCatalog(
11
+ source: string,
12
+ fetchImpl: typeof fetch = globalThis.fetch,
13
+ ): Promise<PortalCatalog> {
14
+ const trimmed = source.trim();
15
+ if (!trimmed) {
16
+ throw new Error("evergreen.portalExportSource must be a non-empty string");
17
+ }
18
+
19
+ if (/^https?:\/\//i.test(trimmed)) {
20
+ if (typeof fetchImpl !== "function") {
21
+ throw new Error("fetch is not available to load a remote portal export");
22
+ }
23
+ const response = await fetchImpl(trimmed);
24
+ if (!response.ok) {
25
+ throw new Error(`failed to fetch portal export from ${trimmed}: HTTP ${response.status}`);
26
+ }
27
+ return parsePortalCatalog(await response.json());
28
+ }
29
+
30
+ const fileUrl = trimmed.startsWith("file:") ? trimmed : pathToFileURL(trimmed).href;
31
+ const text = await readFile(new URL(fileUrl), "utf8");
32
+ return parsePortalCatalog(JSON.parse(text));
33
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,37 @@
1
+ import {
2
+ coreServices,
3
+ createBackendPlugin,
4
+ } from "@backstage/backend-plugin-api";
5
+
6
+ import { createRouter } from "./router";
7
+
8
+ /**
9
+ * Backend plugin that exposes the Evergreen portal export to the frontend card.
10
+ * Reads a published `evergreen.portal.v1` JSON file or URL. Never scans a repository.
11
+ */
12
+ export const evergreenPlugin = createBackendPlugin({
13
+ pluginId: "evergreen",
14
+ register(env) {
15
+ env.registerInit({
16
+ deps: {
17
+ httpRouter: coreServices.httpRouter,
18
+ logger: coreServices.logger,
19
+ config: coreServices.rootConfig,
20
+ },
21
+ async init({ httpRouter, logger, config }) {
22
+ const portalExportSource = config.getString("evergreen.portalExportSource");
23
+ httpRouter.use(
24
+ await createRouter({
25
+ logger,
26
+ portalExportSource,
27
+ }),
28
+ );
29
+ httpRouter.addAuthPolicy({
30
+ path: "/health",
31
+ allow: "unauthenticated",
32
+ });
33
+ logger.info(`Evergreen portal export source: ${portalExportSource}`);
34
+ },
35
+ });
36
+ },
37
+ });
package/src/router.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { LoggerService } from "@backstage/backend-plugin-api";
2
+ import { InputError, NotFoundError } from "@backstage/errors";
3
+ import express from "express";
4
+ import Router from "express-promise-router";
5
+
6
+ import { findService, type PortalCatalog } from "@evergreen-dev/backstage-plugin-common";
7
+ import { loadPortalCatalog } from "./loadPortal";
8
+
9
+ export interface RouterOptions {
10
+ logger: LoggerService;
11
+ /** Absolute path or http(s) URL of the portal export. */
12
+ portalExportSource: string;
13
+ /** Optional fetch override for tests. */
14
+ fetchImpl?: typeof fetch;
15
+ }
16
+
17
+ export async function createRouter(options: RouterOptions): Promise<express.Router> {
18
+ const { logger, portalExportSource, fetchImpl } = options;
19
+ const router = Router();
20
+ router.use(express.json());
21
+
22
+ async function catalog(): Promise<PortalCatalog> {
23
+ try {
24
+ return await loadPortalCatalog(portalExportSource, fetchImpl);
25
+ } catch (error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ logger.error(`Failed to load Evergreen portal export: ${message}`);
28
+ throw error;
29
+ }
30
+ }
31
+
32
+ router.get("/health", (_req, res) => {
33
+ res.json({ status: "ok" });
34
+ });
35
+
36
+ router.get("/catalog", async (_req, res) => {
37
+ res.json(await catalog());
38
+ });
39
+
40
+ router.get("/services", async (req, res) => {
41
+ const servicePath = typeof req.query.path === "string" ? req.query.path : undefined;
42
+ if (!servicePath || servicePath.trim() === "") {
43
+ throw new InputError("query parameter path is required");
44
+ }
45
+ const match = findService(await catalog(), servicePath);
46
+ if (!match) {
47
+ throw new NotFoundError(`no Evergreen portal service matches path ${JSON.stringify(servicePath)}`);
48
+ }
49
+ res.json(match);
50
+ });
51
+
52
+ return router;
53
+ }
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type {
2
+ PortalCatalog,
3
+ PortalService,
4
+ PortalSeverityCounts,
5
+ Severity,
6
+ } from "@evergreen-dev/backstage-plugin-common";
7
+
8
+ export {
9
+ PORTAL_SCHEMA_VERSION,
10
+ SERVICE_PATH_ANNOTATION,
11
+ SEVERITIES,
12
+ findService,
13
+ highestSeverity,
14
+ parsePortalCatalog,
15
+ totalFindings,
16
+ } from "@evergreen-dev/backstage-plugin-common";
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.