@nospt/plugin-dev-ai-hub-backend 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The backend half of **Dev AI Hub**. It reads `AiResource` entities from the Backstage catalog on demand, resolves each resource's body from its `backstage.io/source-location`, and records lightweight install/view telemetry.
4
4
 
5
- > **Requires Backstage 1.51 or later.** The `AiResource` kind comes from Backstage's own alpha catalog module, which is not available before that.
5
+ > **Requires Backstage 1.54 or later.** The `AiResource` kind comes from Backstage's own alpha catalog module, and these packages depend on `@backstage/catalog-model@^1.10.0` — first shipped in 1.54.0, along with the `plugin` and `marketplace` subtypes.
6
6
 
7
7
  ## How it works
8
8
 
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "devAiHub": {
6
+ "type": "object",
7
+ "properties": {
8
+ "telemetry": {
9
+ "type": "object",
10
+ "properties": {
11
+ "salt": {
12
+ "type": "string",
13
+ "description": "Stable salt used to hash the caller identity recorded on telemetry events. Optional: when unset, the plugin generates a salt on first start and persists it in its own database, which keeps hashes stable across restarts. Set it explicitly to keep the salt outside the database it protects — and then it must not change across restarts/deploys, or historical per-day dedup keys become unrecoverable.",
14
+ "visibility": "secret"
15
+ }
16
+ },
17
+ "description": "Install telemetry (ADR-0007). The write path is always active — there is no feature flag — so a salt is always in play; the only question is where it comes from."
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -22,7 +22,7 @@ function createRouter(options) {
22
22
  { filter: { kind: "AiResource" } },
23
23
  { credentials }
24
24
  );
25
- const items = entities.map(pluginDevAiHubCommon.toResourceSummary).filter((s) => s !== void 0);
25
+ const items = pluginDevAiHubCommon.toResourceSummaries(entities);
26
26
  res.json({ items });
27
27
  } catch (error) {
28
28
  if (error.name === "AuthenticationError") {
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import express from 'express';\nimport archiver from 'archiver';\nimport type {\n HttpAuthService,\n LoggerService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport type { TelemetryStore } from './database/TelemetryStore';\nimport type { ResourceSummary } from '@nospt/plugin-dev-ai-hub-common';\nimport {\n TelemetryEventInputSchema,\n toResourceSummary,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { hashActor } from './service/telemetryHash';\nimport {\n SOURCE_LOCATION_ANNOTATION,\n contentTypeFor,\n parseUrlTarget,\n resolveBody,\n} from './service/bodyResolver';\n\ninterface RouterOptions {\n logger: LoggerService;\n telemetryStore: TelemetryStore;\n telemetrySalt: string;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n reader: UrlReaderService;\n}\n\nexport function createRouter(options: RouterOptions): express.Router {\n const { telemetryStore, telemetrySalt, catalog, httpAuth, reader } = options;\n const router = express.Router();\n\n router.use(express.json());\n\n // ── Resources (v2 — catalog-backed, ADR-0001) ─────────────────────────────\n\n router.get('/resources', async (req, res) => {\n try {\n const credentials = await httpAuth.credentials(req);\n const { items: entities } = await catalog.getEntities(\n { filter: { kind: 'AiResource' } },\n { credentials },\n );\n const items = entities\n .map(toResourceSummary)\n .filter((s): s is ResourceSummary => s !== undefined);\n res.json({ items });\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`Failed to list resources: ${error}`);\n res.status(500).json({ error: 'Failed to list resources' });\n }\n });\n\n // ── Body resolver (v2 — ADR-0002/0006/0009) ───────────────────────────────\n\n const safeFilename = (name: string) => name.replace(/[^\\w.-]+/g, '_');\n\n const sendBodyFile = (\n res: express.Response,\n name: string,\n content: Buffer,\n download: boolean,\n ) => {\n res.setHeader('Content-Type', contentTypeFor(name));\n if (download) {\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${safeFilename(name.split('/').pop() ?? name)}\"`,\n );\n }\n res.send(content);\n };\n\n const handleBodyRequest = async (\n req: express.Request,\n res: express.Response,\n filename?: string,\n ) => {\n try {\n // Re-fetch as the caller: catalog visibility is the only gate (ADR-0006).\n const credentials = await httpAuth.credentials(req);\n const entity = await catalog.getEntityByRef(req.params.ref, {\n credentials,\n });\n if (!entity) {\n res.status(404).json({ error: 'Resource not found' });\n return;\n }\n\n const sourceLocation =\n entity.metadata.annotations?.[SOURCE_LOCATION_ANNOTATION];\n const target = sourceLocation && parseUrlTarget(sourceLocation);\n if (!target) {\n res.status(404).json({ error: 'Resource has no content location' });\n return;\n }\n\n let body;\n try {\n body = await resolveBody(reader, target);\n } catch (error) {\n if ((error as Error).name === 'NotFoundError') {\n res.status(404).json({ error: 'Resource content not found' });\n return;\n }\n options.logger.error(\n `Body fetch failed for ${req.params.ref}: ${error}`,\n );\n res\n .status(502)\n .json({ error: 'Failed to fetch resource content from source' });\n return;\n }\n\n const download = req.query.download === 'true';\n\n if (filename !== undefined) {\n const file =\n body.kind === 'tree'\n ? body.files.find(f => f.path === filename)\n : undefined;\n if (!file) {\n res.status(404).json({ error: 'File not found in resource body' });\n return;\n }\n sendBodyFile(res, filename, await file.content(), download);\n return;\n }\n\n if (body.kind === 'file') {\n sendBodyFile(res, body.name, body.content, download);\n return;\n }\n\n if (!download) {\n if (!body.entryPath) {\n res.status(404).json({ error: 'Resource body has no entry file' });\n return;\n }\n const entry = body.files.find(f => f.path === body.entryPath)!;\n sendBodyFile(res, body.entryPath, await entry.content(), false);\n return;\n }\n\n // Multi-file artifact: one zip (ADR-0009 — interim, not the golden road).\n if (body.files.length === 1) {\n sendBodyFile(\n res,\n body.files[0].path,\n await body.files[0].content(),\n true,\n );\n return;\n }\n res.setHeader('Content-Type', 'application/zip');\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${safeFilename(entity.metadata.name)}.zip\"`,\n );\n const archive = archiver('zip');\n archive.on('error', error => {\n options.logger.error(\n `Zip assembly failed for ${req.params.ref}: ${error}`,\n );\n res.destroy(error);\n });\n archive.pipe(res);\n for (const file of body.files) {\n archive.append(await file.content(), { name: file.path });\n }\n await archive.finalize();\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(\n `Body resolution failed for ${req.params.ref}: ${error}`,\n );\n res.status(500).json({ error: 'Failed to resolve resource body' });\n }\n };\n\n router.get('/entity/:ref/raw', (req, res) => handleBodyRequest(req, res));\n router.get('/entity/:ref/raw/:filename', (req, res) =>\n handleBodyRequest(req, res, req.params.filename),\n );\n\n // ── Telemetry (v2 — ADR-0007) ──────────────────────────────────────────────\n\n router.post('/telemetry', async (req, res) => {\n try {\n const parsed = TelemetryEventInputSchema.safeParse(req.body);\n if (!parsed.success) {\n res.status(400).json({ error: parsed.error.message });\n return;\n }\n\n const credentials = await httpAuth.credentials(req);\n const entity = await catalog.getEntityByRef(parsed.data.ref, {\n credentials,\n });\n if (!entity) {\n res.status(404).json({ error: 'Resource not found' });\n return;\n }\n\n await telemetryStore.record({\n entityRef: parsed.data.ref,\n action: parsed.data.action,\n actorHash: hashActor(credentials, telemetrySalt),\n tool: parsed.data.tool,\n });\n res.sendStatus(204);\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`POST /telemetry failed: ${error}`);\n res.status(500).json({ error: 'Failed to record telemetry event' });\n }\n });\n\n router.get('/telemetry/:ref', async (req, res) => {\n try {\n await httpAuth.credentials(req);\n const counts = await telemetryStore.getCounts(req.params.ref);\n res.json(counts);\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`GET /telemetry/${req.params.ref} failed: ${error}`);\n res.status(500).json({ error: 'Failed to fetch telemetry counts' });\n }\n });\n\n return router;\n}\n"],"names":["express","toResourceSummary","contentTypeFor","SOURCE_LOCATION_ANNOTATION","parseUrlTarget","resolveBody","archiver","TelemetryEventInputSchema","hashActor"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import express from 'express';\nimport archiver from 'archiver';\nimport type {\n HttpAuthService,\n LoggerService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogService } from '@backstage/plugin-catalog-node';\nimport type { TelemetryStore } from './database/TelemetryStore';\nimport {\n TelemetryEventInputSchema,\n toResourceSummaries,\n} from '@nospt/plugin-dev-ai-hub-common';\nimport { hashActor } from './service/telemetryHash';\nimport {\n SOURCE_LOCATION_ANNOTATION,\n contentTypeFor,\n parseUrlTarget,\n resolveBody,\n} from './service/bodyResolver';\n\ninterface RouterOptions {\n logger: LoggerService;\n telemetryStore: TelemetryStore;\n telemetrySalt: string;\n catalog: CatalogService;\n httpAuth: HttpAuthService;\n reader: UrlReaderService;\n}\n\nexport function createRouter(options: RouterOptions): express.Router {\n const { telemetryStore, telemetrySalt, catalog, httpAuth, reader } = options;\n const router = express.Router();\n\n router.use(express.json());\n\n // ── Resources (v2 — catalog-backed, ADR-0001) ─────────────────────────────\n\n router.get('/resources', async (req, res) => {\n try {\n const credentials = await httpAuth.credentials(req);\n const { items: entities } = await catalog.getEntities(\n { filter: { kind: 'AiResource' } },\n { credentials },\n );\n const items = toResourceSummaries(entities);\n res.json({ items });\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`Failed to list resources: ${error}`);\n res.status(500).json({ error: 'Failed to list resources' });\n }\n });\n\n // ── Body resolver (v2 — ADR-0002/0006/0009) ───────────────────────────────\n\n const safeFilename = (name: string) => name.replace(/[^\\w.-]+/g, '_');\n\n const sendBodyFile = (\n res: express.Response,\n name: string,\n content: Buffer,\n download: boolean,\n ) => {\n res.setHeader('Content-Type', contentTypeFor(name));\n if (download) {\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${safeFilename(name.split('/').pop() ?? name)}\"`,\n );\n }\n res.send(content);\n };\n\n const handleBodyRequest = async (\n req: express.Request,\n res: express.Response,\n filename?: string,\n ) => {\n try {\n // Re-fetch as the caller: catalog visibility is the only gate (ADR-0006).\n const credentials = await httpAuth.credentials(req);\n const entity = await catalog.getEntityByRef(req.params.ref, {\n credentials,\n });\n if (!entity) {\n res.status(404).json({ error: 'Resource not found' });\n return;\n }\n\n const sourceLocation =\n entity.metadata.annotations?.[SOURCE_LOCATION_ANNOTATION];\n const target = sourceLocation && parseUrlTarget(sourceLocation);\n if (!target) {\n res.status(404).json({ error: 'Resource has no content location' });\n return;\n }\n\n let body;\n try {\n body = await resolveBody(reader, target);\n } catch (error) {\n if ((error as Error).name === 'NotFoundError') {\n res.status(404).json({ error: 'Resource content not found' });\n return;\n }\n options.logger.error(\n `Body fetch failed for ${req.params.ref}: ${error}`,\n );\n res\n .status(502)\n .json({ error: 'Failed to fetch resource content from source' });\n return;\n }\n\n const download = req.query.download === 'true';\n\n if (filename !== undefined) {\n const file =\n body.kind === 'tree'\n ? body.files.find(f => f.path === filename)\n : undefined;\n if (!file) {\n res.status(404).json({ error: 'File not found in resource body' });\n return;\n }\n sendBodyFile(res, filename, await file.content(), download);\n return;\n }\n\n if (body.kind === 'file') {\n sendBodyFile(res, body.name, body.content, download);\n return;\n }\n\n if (!download) {\n if (!body.entryPath) {\n res.status(404).json({ error: 'Resource body has no entry file' });\n return;\n }\n const entry = body.files.find(f => f.path === body.entryPath)!;\n sendBodyFile(res, body.entryPath, await entry.content(), false);\n return;\n }\n\n // Multi-file artifact: one zip (ADR-0009 — interim, not the golden road).\n if (body.files.length === 1) {\n sendBodyFile(\n res,\n body.files[0].path,\n await body.files[0].content(),\n true,\n );\n return;\n }\n res.setHeader('Content-Type', 'application/zip');\n res.setHeader(\n 'Content-Disposition',\n `attachment; filename=\"${safeFilename(entity.metadata.name)}.zip\"`,\n );\n const archive = archiver('zip');\n archive.on('error', error => {\n options.logger.error(\n `Zip assembly failed for ${req.params.ref}: ${error}`,\n );\n res.destroy(error);\n });\n archive.pipe(res);\n for (const file of body.files) {\n archive.append(await file.content(), { name: file.path });\n }\n await archive.finalize();\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(\n `Body resolution failed for ${req.params.ref}: ${error}`,\n );\n res.status(500).json({ error: 'Failed to resolve resource body' });\n }\n };\n\n router.get('/entity/:ref/raw', (req, res) => handleBodyRequest(req, res));\n router.get('/entity/:ref/raw/:filename', (req, res) =>\n handleBodyRequest(req, res, req.params.filename),\n );\n\n // ── Telemetry (v2 — ADR-0007) ──────────────────────────────────────────────\n\n router.post('/telemetry', async (req, res) => {\n try {\n const parsed = TelemetryEventInputSchema.safeParse(req.body);\n if (!parsed.success) {\n res.status(400).json({ error: parsed.error.message });\n return;\n }\n\n const credentials = await httpAuth.credentials(req);\n const entity = await catalog.getEntityByRef(parsed.data.ref, {\n credentials,\n });\n if (!entity) {\n res.status(404).json({ error: 'Resource not found' });\n return;\n }\n\n await telemetryStore.record({\n entityRef: parsed.data.ref,\n action: parsed.data.action,\n actorHash: hashActor(credentials, telemetrySalt),\n tool: parsed.data.tool,\n });\n res.sendStatus(204);\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`POST /telemetry failed: ${error}`);\n res.status(500).json({ error: 'Failed to record telemetry event' });\n }\n });\n\n router.get('/telemetry/:ref', async (req, res) => {\n try {\n await httpAuth.credentials(req);\n const counts = await telemetryStore.getCounts(req.params.ref);\n res.json(counts);\n } catch (error) {\n if ((error as Error).name === 'AuthenticationError') {\n res.status(401).json({ error: 'Authentication required' });\n return;\n }\n options.logger.error(`GET /telemetry/${req.params.ref} failed: ${error}`);\n res.status(500).json({ error: 'Failed to fetch telemetry counts' });\n }\n });\n\n return router;\n}\n"],"names":["express","toResourceSummaries","contentTypeFor","SOURCE_LOCATION_ANNOTATION","parseUrlTarget","resolveBody","archiver","TelemetryEventInputSchema","hashActor"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nospt/plugin-dev-ai-hub-backend",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Backend plugin for Dev AI Hub",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.cjs.js",
@@ -31,10 +31,10 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "migrations",
34
- "config.d.ts",
34
+ "config.schema.json",
35
35
  "README.md"
36
36
  ],
37
- "configSchema": "config.d.ts",
37
+ "configSchema": "config.schema.json",
38
38
  "backstage": {
39
39
  "role": "backend-plugin",
40
40
  "pluginId": "dev-ai-hub",
@@ -58,23 +58,23 @@
58
58
  "tsc": "tsc --noEmit"
59
59
  },
60
60
  "dependencies": {
61
- "@backstage/backend-plugin-api": "^1.9.1",
62
- "@backstage/catalog-model": "^1.9.0",
63
- "@backstage/plugin-catalog-node": "^2.2.2",
64
- "@nospt/plugin-dev-ai-hub-common": "^1.0.1",
61
+ "@backstage/backend-plugin-api": "^1.10.0",
62
+ "@backstage/catalog-model": "^1.10.0",
63
+ "@backstage/plugin-catalog-node": "^2.2.4",
64
+ "@nospt/plugin-dev-ai-hub-common": "^1.0.3",
65
65
  "archiver": "^6.0.0",
66
66
  "express": "^5.2.1",
67
- "js-yaml": "^4.1.0",
67
+ "js-yaml": "^4.3.1",
68
68
  "zod": "^3.22.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@backstage/backend-defaults": "^0.17.1",
72
- "@backstage/backend-test-utils": "^1.11.3",
73
- "@backstage/cli": "^0.36.2",
74
- "@backstage/plugin-auth-backend": "0.29.0",
75
- "@backstage/plugin-auth-backend-module-guest-provider": "0.2.10",
76
- "@backstage/plugin-catalog-backend": "^3.8.0",
77
- "@backstage/plugin-catalog-backend-module-ai-model": "^0.1.1",
71
+ "@backstage/backend-defaults": "^0.17.7",
72
+ "@backstage/backend-test-utils": "^1.11.6",
73
+ "@backstage/cli": "^0.36.5",
74
+ "@backstage/plugin-auth-backend": "^0.30.0",
75
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.22",
76
+ "@backstage/plugin-catalog-backend": "^3.9.0",
77
+ "@backstage/plugin-catalog-backend-module-ai-model": "^0.1.3",
78
78
  "@types/archiver": "^6.0.0",
79
79
  "@types/express": "^4.17.21",
80
80
  "@types/jest": "^29.0.0",
package/config.d.ts DELETED
@@ -1,22 +0,0 @@
1
- export interface Config {
2
- devAiHub?: {
3
- /**
4
- * Install telemetry (ADR-0007). The write path is always active — there
5
- * is no feature flag — so a salt is always in play; the only question is
6
- * where it comes from.
7
- */
8
- telemetry?: {
9
- /**
10
- * Stable salt used to hash the caller identity recorded on telemetry
11
- * events. Optional: when unset, the plugin generates a salt on first
12
- * start and persists it in its own database, which keeps hashes stable
13
- * across restarts. Set it explicitly to keep the salt outside the
14
- * database it protects — and then it must not change across
15
- * restarts/deploys, or historical per-day dedup keys become
16
- * unrecoverable.
17
- * @visibility secret
18
- */
19
- salt?: string;
20
- };
21
- };
22
- }