@axis-backstage/plugin-readme-backend 0.11.0 → 0.13.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,26 @@
1
1
  # @axis-backstage/plugin-readme-backend
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 84e60e6: A configuration has been added to the readme backend that makes it possible to
8
+ configure which files to look for and the exact order. Example:
9
+
10
+ ```yaml
11
+ readme:
12
+ -fileNames:
13
+ - README.txt
14
+ - README.text
15
+ - README.markdown
16
+ ```
17
+
18
+ ## 0.12.0
19
+
20
+ ### Minor Changes
21
+
22
+ - ede8341: Updated to Backstage v1.36.1.
23
+
3
24
  ## 0.11.0
4
25
 
5
26
  ### Minor Changes
package/README.md CHANGED
@@ -32,6 +32,20 @@ const backend = createBackend();
32
32
  backend.start();
33
33
  ```
34
34
 
35
+ ### Configuration
36
+
37
+ This plugin does not require any configuration. It has a default configuration
38
+ with a set of README file names that it will look for in the entity source location. This actual file to use and the order to look for them can be
39
+ configured in the `app-config.yaml` file using the "readme.fileNames" configuration key. This is a simple list of file names to look for in the entity source location.
40
+
41
+ ```yaml
42
+ readme:
43
+ -fileNames:
44
+ - README.txt
45
+ - README.text
46
+ - README.markdown
47
+ ```
48
+
35
49
  ### Troubleshooting
36
50
 
37
51
  If the backend fails to provide README content for an entity, it could be due to several reasons.
package/config.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface Config {
2
+ /**
3
+ * Configuration options for the Readme backend plugin
4
+ */
5
+ readme?: {
6
+ /**
7
+ * Optional list of file names to try. Specifies the file names to try
8
+ * when looking for a README file and which order to use.
9
+ */
10
+ fileNames?: string[];
11
+ };
12
+ }
package/dist/index.cjs.js CHANGED
@@ -2,167 +2,9 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var backendPluginApi = require('@backstage/backend-plugin-api');
6
- var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
7
- var cache = require('@backstage/backend-defaults/cache');
8
- var integration = require('@backstage/integration');
9
- var catalogModel = require('@backstage/catalog-model');
10
- var catalogClient = require('@backstage/catalog-client');
11
- var errors = require('@backstage/errors');
12
- var express = require('express');
13
- var Router = require('express-promise-router');
5
+ var plugin = require('./plugin.cjs.js');
14
6
 
15
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
16
7
 
17
- var express__default = /*#__PURE__*/_interopDefaultCompat(express);
18
- var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
19
8
 
20
- const DETECT_SYMLINKS_REGEX = "^(w+|.|/|-)+$";
21
- const isSymLink = (content) => {
22
- const lines = content.split("\n");
23
- if (lines.length > 1) return false;
24
- const line = lines[0];
25
- if (line.includes(" ")) return false;
26
- const regex = RegExp(DETECT_SYMLINKS_REGEX);
27
- return regex.test(content);
28
- };
29
-
30
- const NOT_FOUND_PLACEHOLDER = "NOT_FOUND";
31
- const DEFAULT_TTL = 1800 * 1e3;
32
- const README_TYPES = [
33
- { name: "README", type: "text/plain" },
34
- { name: "README.md", type: "text/markdown" },
35
- { name: "README.rst", type: "text/plain" },
36
- { name: "README.txt", type: "text/plain" },
37
- { name: "README.MD", type: "text/markdown" }
38
- ];
39
-
40
- async function createRouter(options) {
41
- const { auth, logger, config, reader, discovery } = options;
42
- const catalogClient$1 = new catalogClient.CatalogClient({ discoveryApi: discovery });
43
- const pluginCache = cache.CacheManager.fromConfig(config).forPlugin("readme");
44
- const cache$1 = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });
45
- logger.info("Initializing readme backend");
46
- const integrations = integration.ScmIntegrations.fromConfig(config);
47
- const router = Router__default.default();
48
- router.use(express__default.default.json());
49
- router.get("/health", (_, response) => {
50
- response.json({ status: "ok" });
51
- });
52
- router.get("/:kind/:namespace/:name", async (request, response) => {
53
- const { kind, namespace, name } = request.params;
54
- const entityRef = catalogModel.stringifyEntityRef({ kind, namespace, name });
55
- const cacheDoc = await cache$1.get(entityRef);
56
- if (cacheDoc && cacheDoc.name === NOT_FOUND_PLACEHOLDER) {
57
- throw new errors.NotFoundError("Readme could not be found");
58
- }
59
- if (cacheDoc) {
60
- logger.info(`Loading README for ${entityRef} from cache.`);
61
- response.type(cacheDoc.type);
62
- response.send(cacheDoc.content);
63
- return;
64
- }
65
- const { token } = await auth.getPluginRequestToken({
66
- onBehalfOf: await auth.getOwnServiceCredentials(),
67
- targetPluginId: "catalog"
68
- });
69
- const entity = await catalogClient$1.getEntityByRef(entityRef, { token });
70
- if (!entity) {
71
- logger.info(`No integration found for ${entityRef}`);
72
- response.status(500).json({ error: `No integration found for ${entityRef}` });
73
- return;
74
- }
75
- const source = catalogModel.getEntitySourceLocation(entity);
76
- if (!source || source.type !== "url") {
77
- const errorMessage = `Not valid location for ${source.target}`;
78
- logger.info(errorMessage);
79
- throw new errors.NotFoundError(errorMessage);
80
- }
81
- const integration = integrations.byUrl(source.target);
82
- if (!integration) {
83
- logger.info(`No integration found for ${source.target}`);
84
- response.status(500).json({ error: `No integration found for ${source.target}` });
85
- return;
86
- }
87
- for (const fileType of README_TYPES) {
88
- const url = integration.resolveUrl({
89
- url: fileType.name,
90
- base: source.target
91
- });
92
- let content;
93
- try {
94
- logger.debug(`Trying README location: ${url} for ${entityRef} `);
95
- response.type(fileType.type);
96
- const urlResponse = await reader.readUrl(url);
97
- content = (await urlResponse.buffer()).toString("utf-8");
98
- if (isSymLink(content)) {
99
- const symLinkUrl = integration.resolveUrl({
100
- url: content,
101
- base: source.target
102
- });
103
- const symLinkUrlResponse = await reader.readUrl(symLinkUrl);
104
- content = (await symLinkUrlResponse.buffer()).toString("utf-8");
105
- }
106
- cache$1.set(entityRef, {
107
- name: fileType.name,
108
- type: fileType.type,
109
- content
110
- });
111
- logger.info(
112
- `Found README for ${entityRef}: ${url} type ${fileType.type}`
113
- );
114
- response.send(content);
115
- return;
116
- } catch (error) {
117
- if (errors.isError(error) && error.name === "NotFoundError") {
118
- continue;
119
- } else {
120
- response.status(500).json({
121
- error: `Readme failure: ${error}`
122
- });
123
- return;
124
- }
125
- }
126
- }
127
- logger.info(`Readme not found for ${entityRef}`);
128
- cache$1.set(entityRef, {
129
- name: NOT_FOUND_PLACEHOLDER,
130
- type: "",
131
- content: ""
132
- });
133
- throw new errors.NotFoundError("Readme could not be found");
134
- });
135
- const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config });
136
- router.use(middleware.error());
137
- return router;
138
- }
139
-
140
- const readmePlugin = backendPluginApi.createBackendPlugin({
141
- pluginId: "readme",
142
- register(env) {
143
- env.registerInit({
144
- deps: {
145
- auth: backendPluginApi.coreServices.auth,
146
- config: backendPluginApi.coreServices.rootConfig,
147
- discovery: backendPluginApi.coreServices.discovery,
148
- httpRouter: backendPluginApi.coreServices.httpRouter,
149
- logger: backendPluginApi.coreServices.logger,
150
- reader: backendPluginApi.coreServices.urlReader
151
- },
152
- async init({ auth, logger, config, reader, discovery, httpRouter }) {
153
- httpRouter.use(
154
- await createRouter({
155
- auth,
156
- logger,
157
- config,
158
- reader,
159
- discovery
160
- })
161
- );
162
- }
163
- });
164
- }
165
- });
166
-
167
- exports.default = readmePlugin;
9
+ exports.default = plugin.readmePlugin;
168
10
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/lib.ts","../src/service/constants.ts","../src/service/router.ts","../src/plugin.ts"],"sourcesContent":["const DETECT_SYMLINKS_REGEX = '^(w+|.|/|-)+$';\n\nexport const isSymLink = (content: string): boolean => {\n const lines = content.split('\\n');\n if (lines.length > 1) return false;\n const line = lines[0];\n if (line.includes(' ')) return false;\n\n const regex = RegExp(DETECT_SYMLINKS_REGEX);\n return regex.test(content);\n};\n","import { FileType } from './types';\n\n// Cache placeholder for entities where no readme\nexport const NOT_FOUND_PLACEHOLDER = 'NOT_FOUND';\nexport const DEFAULT_TTL = 1800 * 1000;\n\nexport const README_TYPES: FileType[] = [\n { name: 'README', type: 'text/plain' },\n { name: 'README.md', type: 'text/markdown' },\n { name: 'README.rst', type: 'text/plain' },\n { name: 'README.txt', type: 'text/plain' },\n { name: 'README.MD', type: 'text/markdown' },\n];\n","import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { CacheManager } from '@backstage/backend-defaults/cache';\nimport {\n AuthService,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ScmIntegrations } from '@backstage/integration';\nimport {\n getEntitySourceLocation,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { isError, NotFoundError } from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { isSymLink } from '../lib';\nimport { DEFAULT_TTL, NOT_FOUND_PLACEHOLDER, README_TYPES } from './constants';\nimport { ReadmeFile } from './types';\n\n/**\n * Constructs a readme router.\n *\n */\ninterface RouterOptions {\n auth: AuthService;\n config: RootConfigService;\n discovery: DiscoveryService;\n logger: LoggerService;\n reader: UrlReaderService;\n}\n\n/**\n * Constructs a readme router.\n *\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { auth, logger, config, reader, discovery } = options;\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n const pluginCache = CacheManager.fromConfig(config).forPlugin('readme');\n const cache = pluginCache.getClient({ defaultTtl: DEFAULT_TTL });\n\n logger.info('Initializing readme backend');\n const integrations = ScmIntegrations.fromConfig(config);\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n response.json({ status: 'ok' });\n });\n\n router.get('/:kind/:namespace/:name', async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const cacheDoc = (await cache.get(entityRef)) as ReadmeFile | undefined;\n\n if (cacheDoc && cacheDoc.name === NOT_FOUND_PLACEHOLDER) {\n throw new NotFoundError('Readme could not be found');\n }\n if (cacheDoc) {\n logger.info(`Loading README for ${entityRef} from cache.`);\n response.type(cacheDoc.type);\n response.send(cacheDoc.content);\n return;\n }\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n if (!entity) {\n logger.info(`No integration found for ${entityRef}`);\n response\n .status(500)\n .json({ error: `No integration found for ${entityRef}` });\n return;\n }\n const source = getEntitySourceLocation(entity);\n\n if (!source || source.type !== 'url') {\n const errorMessage = `Not valid location for ${source.target}`;\n logger.info(errorMessage);\n throw new NotFoundError(errorMessage);\n }\n const integration = integrations.byUrl(source.target);\n\n if (!integration) {\n logger.info(`No integration found for ${source.target}`);\n response\n .status(500)\n .json({ error: `No integration found for ${source.target}` });\n return;\n }\n\n for (const fileType of README_TYPES) {\n const url = integration.resolveUrl({\n url: fileType.name,\n base: source.target,\n });\n\n let content;\n\n try {\n logger.debug(`Trying README location: ${url} for ${entityRef} `);\n response.type(fileType.type);\n\n const urlResponse = await reader.readUrl(url);\n content = (await urlResponse.buffer()).toString('utf-8');\n\n if (isSymLink(content)) {\n const symLinkUrl = integration.resolveUrl({\n url: content,\n base: source.target,\n });\n const symLinkUrlResponse = await reader.readUrl(symLinkUrl);\n content = (await symLinkUrlResponse.buffer()).toString('utf-8');\n }\n\n cache.set(entityRef, {\n name: fileType.name,\n type: fileType.type,\n content: content,\n });\n logger.info(\n `Found README for ${entityRef}: ${url} type ${fileType.type}`,\n );\n response.send(content);\n return;\n } catch (error: unknown) {\n if (isError(error) && error.name === 'NotFoundError') {\n continue;\n } else {\n response.status(500).json({\n error: `Readme failure: ${error}`,\n });\n return;\n }\n }\n }\n logger.info(`Readme not found for ${entityRef}`);\n cache.set(entityRef, {\n name: NOT_FOUND_PLACEHOLDER,\n type: '',\n content: '',\n });\n throw new NotFoundError('Readme could not be found');\n });\n\n const middleware = MiddlewareFactory.create({ logger, config });\n router.use(middleware.error());\n return router;\n}\n","import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * The Readme backend plugin.\n *\n * @public\n */\nexport const readmePlugin = createBackendPlugin({\n pluginId: 'readme',\n register(env) {\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n reader: coreServices.urlReader,\n },\n async init({ auth, logger, config, reader, discovery, httpRouter }) {\n httpRouter.use(\n await createRouter({\n auth,\n logger,\n config,\n reader,\n discovery,\n }),\n );\n },\n });\n },\n});\n"],"names":["catalogClient","CatalogClient","CacheManager","cache","ScmIntegrations","Router","express","stringifyEntityRef","NotFoundError","getEntitySourceLocation","isError","MiddlewareFactory","createBackendPlugin","coreServices"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,6 @@ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
5
5
  *
6
6
  * @public
7
7
  */
8
- declare const readmePlugin: _backstage_backend_plugin_api.BackendFeatureCompat;
8
+ declare const readmePlugin: _backstage_backend_plugin_api.BackendFeature;
9
9
 
10
10
  export { readmePlugin as default };
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const DETECT_SYMLINKS_REGEX = "^(w+|.|/|-)+$";
4
+ const isSymLink = (content) => {
5
+ const lines = content.split("\n");
6
+ if (lines.length > 1) return false;
7
+ const line = lines[0];
8
+ if (line.includes(" ")) return false;
9
+ const regex = RegExp(DETECT_SYMLINKS_REGEX);
10
+ return regex.test(content);
11
+ };
12
+
13
+ exports.isSymLink = isSymLink;
14
+ //# sourceMappingURL=lib.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.cjs.js","sources":["../src/lib.ts"],"sourcesContent":["const DETECT_SYMLINKS_REGEX = '^(w+|.|/|-)+$';\n\nexport const isSymLink = (content: string): boolean => {\n const lines = content.split('\\n');\n if (lines.length > 1) return false;\n const line = lines[0];\n if (line.includes(' ')) return false;\n\n const regex = RegExp(DETECT_SYMLINKS_REGEX);\n return regex.test(content);\n};\n"],"names":[],"mappings":";;AAAA,MAAM,qBAAwB,GAAA,eAAA;AAEjB,MAAA,SAAA,GAAY,CAAC,OAA6B,KAAA;AACrD,EAAM,MAAA,KAAA,GAAQ,OAAQ,CAAA,KAAA,CAAM,IAAI,CAAA;AAChC,EAAI,IAAA,KAAA,CAAM,MAAS,GAAA,CAAA,EAAU,OAAA,KAAA;AAC7B,EAAM,MAAA,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,EAAA,IAAI,IAAK,CAAA,QAAA,CAAS,GAAG,CAAA,EAAU,OAAA,KAAA;AAE/B,EAAM,MAAA,KAAA,GAAQ,OAAO,qBAAqB,CAAA;AAC1C,EAAO,OAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAC3B;;;;"}
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var router = require('./service/router.cjs.js');
5
+
6
+ const readmePlugin = backendPluginApi.createBackendPlugin({
7
+ pluginId: "readme",
8
+ register(env) {
9
+ env.registerInit({
10
+ deps: {
11
+ auth: backendPluginApi.coreServices.auth,
12
+ config: backendPluginApi.coreServices.rootConfig,
13
+ discovery: backendPluginApi.coreServices.discovery,
14
+ httpRouter: backendPluginApi.coreServices.httpRouter,
15
+ logger: backendPluginApi.coreServices.logger,
16
+ reader: backendPluginApi.coreServices.urlReader,
17
+ cache: backendPluginApi.coreServices.cache
18
+ },
19
+ async init({
20
+ auth,
21
+ logger,
22
+ config,
23
+ reader,
24
+ discovery,
25
+ httpRouter,
26
+ cache
27
+ }) {
28
+ httpRouter.use(
29
+ await router.createRouter({
30
+ auth,
31
+ logger,
32
+ config,
33
+ reader,
34
+ discovery,
35
+ cache
36
+ })
37
+ );
38
+ }
39
+ });
40
+ }
41
+ });
42
+
43
+ exports.readmePlugin = readmePlugin;
44
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './service/router';\n\n/**\n * The Readme backend plugin.\n *\n * @public\n */\nexport const readmePlugin = createBackendPlugin({\n pluginId: 'readme',\n register(env) {\n env.registerInit({\n deps: {\n auth: coreServices.auth,\n config: coreServices.rootConfig,\n discovery: coreServices.discovery,\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n reader: coreServices.urlReader,\n cache: coreServices.cache,\n },\n async init({\n auth,\n logger,\n config,\n reader,\n discovery,\n httpRouter,\n cache,\n }) {\n httpRouter.use(\n await createRouter({\n auth,\n logger,\n config,\n reader,\n discovery,\n cache,\n }),\n );\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouter"],"mappings":";;;;;AAWO,MAAM,eAAeA,oCAAoB,CAAA;AAAA,EAC9C,QAAU,EAAA,QAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,MAAMC,6BAAa,CAAA,IAAA;AAAA,QACnB,QAAQA,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,YAAYA,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,SAAA;AAAA,QACrB,OAAOA,6BAAa,CAAA;AAAA,OACtB;AAAA,MACA,MAAM,IAAK,CAAA;AAAA,QACT,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,SAAA;AAAA,QACA,UAAA;AAAA,QACA;AAAA,OACC,EAAA;AACD,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,mBAAa,CAAA;AAAA,YACjB,IAAA;AAAA,YACA,MAAA;AAAA,YACA,MAAA;AAAA,YACA,MAAA;AAAA,YACA,SAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AAAA;AACF,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const CONFIG_PATH = "readme.fileNames";
4
+ const NOT_FOUND_PLACEHOLDER = "NOT_FOUND";
5
+ const DEFAULT_README_TYPES = [
6
+ { name: "README.md", type: "text/markdown" },
7
+ { name: "README.MD", type: "text/markdown" },
8
+ { name: "README", type: "text/plain" },
9
+ { name: "README.rst", type: "text/plain" },
10
+ { name: "README.txt", type: "text/plain" }
11
+ ];
12
+ function getReadmeTypes(config) {
13
+ const readmeTypes = config.getOptionalStringArray(CONFIG_PATH);
14
+ if (!readmeTypes) {
15
+ return DEFAULT_README_TYPES;
16
+ }
17
+ return readmeTypes.map((name) => {
18
+ const type = name.endsWith(".md") || name.endsWith(".MD") ? "text/markdown" : "text/plain";
19
+ return { name, type };
20
+ });
21
+ }
22
+
23
+ exports.NOT_FOUND_PLACEHOLDER = NOT_FOUND_PLACEHOLDER;
24
+ exports.getReadmeTypes = getReadmeTypes;
25
+ //# sourceMappingURL=constants.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.cjs.js","sources":["../../src/service/constants.ts"],"sourcesContent":["import { RootConfigService } from '@backstage/backend-plugin-api';\nimport { FileType } from './types';\n\nconst CONFIG_PATH = 'readme.fileNames';\n\n// Cache placeholder for entities where no readme\nexport const NOT_FOUND_PLACEHOLDER = 'NOT_FOUND';\n\nconst DEFAULT_README_TYPES: FileType[] = [\n { name: 'README.md', type: 'text/markdown' },\n { name: 'README.MD', type: 'text/markdown' },\n { name: 'README', type: 'text/plain' },\n { name: 'README.rst', type: 'text/plain' },\n { name: 'README.txt', type: 'text/plain' },\n];\n\nexport function getReadmeTypes(config: RootConfigService): FileType[] {\n const readmeTypes = config.getOptionalStringArray(CONFIG_PATH);\n if (!readmeTypes) {\n return DEFAULT_README_TYPES;\n }\n return readmeTypes.map(name => {\n const type =\n name.endsWith('.md') || name.endsWith('.MD')\n ? 'text/markdown'\n : 'text/plain';\n return { name, type };\n });\n}\n"],"names":[],"mappings":";;AAGA,MAAM,WAAc,GAAA,kBAAA;AAGb,MAAM,qBAAwB,GAAA;AAErC,MAAM,oBAAmC,GAAA;AAAA,EACvC,EAAE,IAAA,EAAM,WAAa,EAAA,IAAA,EAAM,eAAgB,EAAA;AAAA,EAC3C,EAAE,IAAA,EAAM,WAAa,EAAA,IAAA,EAAM,eAAgB,EAAA;AAAA,EAC3C,EAAE,IAAA,EAAM,QAAU,EAAA,IAAA,EAAM,YAAa,EAAA;AAAA,EACrC,EAAE,IAAA,EAAM,YAAc,EAAA,IAAA,EAAM,YAAa,EAAA;AAAA,EACzC,EAAE,IAAA,EAAM,YAAc,EAAA,IAAA,EAAM,YAAa;AAC3C,CAAA;AAEO,SAAS,eAAe,MAAuC,EAAA;AACpE,EAAM,MAAA,WAAA,GAAc,MAAO,CAAA,sBAAA,CAAuB,WAAW,CAAA;AAC7D,EAAA,IAAI,CAAC,WAAa,EAAA;AAChB,IAAO,OAAA,oBAAA;AAAA;AAET,EAAO,OAAA,WAAA,CAAY,IAAI,CAAQ,IAAA,KAAA;AAC7B,IAAM,MAAA,IAAA,GACJ,KAAK,QAAS,CAAA,KAAK,KAAK,IAAK,CAAA,QAAA,CAAS,KAAK,CAAA,GACvC,eACA,GAAA,YAAA;AACN,IAAO,OAAA,EAAE,MAAM,IAAK,EAAA;AAAA,GACrB,CAAA;AACH;;;;;"}
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
4
+ var integration = require('@backstage/integration');
5
+ var catalogModel = require('@backstage/catalog-model');
6
+ var catalogClient = require('@backstage/catalog-client');
7
+ var errors = require('@backstage/errors');
8
+ var express = require('express');
9
+ var Router = require('express-promise-router');
10
+ var lib = require('../lib.cjs.js');
11
+ var constants = require('./constants.cjs.js');
12
+
13
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
14
+
15
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
16
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
17
+
18
+ async function createRouter(options) {
19
+ const { auth, logger, config, reader, discovery, cache } = options;
20
+ const catalogClient$1 = new catalogClient.CatalogClient({ discoveryApi: discovery });
21
+ logger.info("Initializing readme backend");
22
+ const integrations = integration.ScmIntegrations.fromConfig(config);
23
+ const router = Router__default.default();
24
+ router.use(express__default.default.json());
25
+ router.get("/health", (_, response) => {
26
+ response.json({ status: "ok" });
27
+ });
28
+ router.get("/:kind/:namespace/:name", async (request, response) => {
29
+ const { kind, namespace, name } = request.params;
30
+ const entityRef = catalogModel.stringifyEntityRef({ kind, namespace, name });
31
+ const cacheDoc = await cache.get(entityRef);
32
+ if (cacheDoc && cacheDoc.name === constants.NOT_FOUND_PLACEHOLDER) {
33
+ throw new errors.NotFoundError("Readme could not be found");
34
+ }
35
+ if (cacheDoc) {
36
+ logger.info(`Loading README for ${entityRef} from cache.`);
37
+ response.type(cacheDoc.type);
38
+ response.send(cacheDoc.content);
39
+ return;
40
+ }
41
+ const { token } = await auth.getPluginRequestToken({
42
+ onBehalfOf: await auth.getOwnServiceCredentials(),
43
+ targetPluginId: "catalog"
44
+ });
45
+ const entity = await catalogClient$1.getEntityByRef(entityRef, { token });
46
+ if (!entity) {
47
+ logger.info(`No integration found for ${entityRef}`);
48
+ response.status(500).json({ error: `No integration found for ${entityRef}` });
49
+ return;
50
+ }
51
+ const source = catalogModel.getEntitySourceLocation(entity);
52
+ if (!source || source.type !== "url") {
53
+ const errorMessage = `Not valid location for ${source.target}`;
54
+ logger.info(errorMessage);
55
+ throw new errors.NotFoundError(errorMessage);
56
+ }
57
+ const integration = integrations.byUrl(source.target);
58
+ if (!integration) {
59
+ logger.info(`No integration found for ${source.target}`);
60
+ response.status(500).json({ error: `No integration found for ${source.target}` });
61
+ return;
62
+ }
63
+ const readmeTypes = constants.getReadmeTypes(config);
64
+ for (const fileType of readmeTypes) {
65
+ const url = integration.resolveUrl({
66
+ url: fileType.name,
67
+ base: source.target
68
+ });
69
+ let content;
70
+ try {
71
+ logger.debug(`Trying README location: ${url} for ${entityRef} `);
72
+ response.type(fileType.type);
73
+ const urlResponse = await reader.readUrl(url);
74
+ content = (await urlResponse.buffer()).toString("utf-8");
75
+ if (lib.isSymLink(content)) {
76
+ const symLinkUrl = integration.resolveUrl({
77
+ url: content,
78
+ base: source.target
79
+ });
80
+ const symLinkUrlResponse = await reader.readUrl(symLinkUrl);
81
+ content = (await symLinkUrlResponse.buffer()).toString("utf-8");
82
+ }
83
+ cache.set(entityRef, {
84
+ name: fileType.name,
85
+ type: fileType.type,
86
+ content
87
+ });
88
+ logger.info(
89
+ `Found README for ${entityRef}: ${url} type ${fileType.type}`
90
+ );
91
+ response.send(content);
92
+ return;
93
+ } catch (error) {
94
+ if (errors.isError(error) && error.name === "NotFoundError") {
95
+ continue;
96
+ } else {
97
+ response.status(500).json({
98
+ error: `Readme failure: ${error}`
99
+ });
100
+ return;
101
+ }
102
+ }
103
+ }
104
+ logger.info(`Readme not found for ${entityRef}`);
105
+ cache.set(entityRef, {
106
+ name: constants.NOT_FOUND_PLACEHOLDER,
107
+ type: "",
108
+ content: ""
109
+ });
110
+ throw new errors.NotFoundError("Readme could not be found");
111
+ });
112
+ const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config });
113
+ router.use(middleware.error());
114
+ return router;
115
+ }
116
+
117
+ exports.createRouter = createRouter;
118
+ //# sourceMappingURL=router.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport {\n AuthService,\n CacheService,\n DiscoveryService,\n LoggerService,\n RootConfigService,\n UrlReaderService,\n} from '@backstage/backend-plugin-api';\nimport { ScmIntegrations } from '@backstage/integration';\nimport {\n getEntitySourceLocation,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { isError, NotFoundError } from '@backstage/errors';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { isSymLink } from '../lib';\nimport { NOT_FOUND_PLACEHOLDER, getReadmeTypes } from './constants';\nimport { ReadmeFile } from './types';\n\n/**\n * Constructs a readme router.\n *\n */\ninterface RouterOptions {\n auth: AuthService;\n config: RootConfigService;\n discovery: DiscoveryService;\n logger: LoggerService;\n reader: UrlReaderService;\n cache: CacheService;\n}\n\n/**\n * Constructs a readme router.\n *\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const { auth, logger, config, reader, discovery, cache } = options;\n const catalogClient = new CatalogClient({ discoveryApi: discovery });\n\n logger.info('Initializing readme backend');\n const integrations = ScmIntegrations.fromConfig(config);\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n response.json({ status: 'ok' });\n });\n\n router.get('/:kind/:namespace/:name', async (request, response) => {\n const { kind, namespace, name } = request.params;\n const entityRef = stringifyEntityRef({ kind, namespace, name });\n const cacheDoc = (await cache.get(entityRef)) as ReadmeFile | undefined;\n\n if (cacheDoc && cacheDoc.name === NOT_FOUND_PLACEHOLDER) {\n throw new NotFoundError('Readme could not be found');\n }\n if (cacheDoc) {\n logger.info(`Loading README for ${entityRef} from cache.`);\n response.type(cacheDoc.type);\n response.send(cacheDoc.content);\n return;\n }\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await catalogClient.getEntityByRef(entityRef, { token });\n if (!entity) {\n logger.info(`No integration found for ${entityRef}`);\n response\n .status(500)\n .json({ error: `No integration found for ${entityRef}` });\n return;\n }\n const source = getEntitySourceLocation(entity);\n\n if (!source || source.type !== 'url') {\n const errorMessage = `Not valid location for ${source.target}`;\n logger.info(errorMessage);\n throw new NotFoundError(errorMessage);\n }\n const integration = integrations.byUrl(source.target);\n\n if (!integration) {\n logger.info(`No integration found for ${source.target}`);\n response\n .status(500)\n .json({ error: `No integration found for ${source.target}` });\n return;\n }\n\n const readmeTypes = getReadmeTypes(config);\n\n for (const fileType of readmeTypes) {\n const url = integration.resolveUrl({\n url: fileType.name,\n base: source.target,\n });\n\n let content;\n\n try {\n logger.debug(`Trying README location: ${url} for ${entityRef} `);\n response.type(fileType.type);\n\n const urlResponse = await reader.readUrl(url);\n content = (await urlResponse.buffer()).toString('utf-8');\n\n if (isSymLink(content)) {\n const symLinkUrl = integration.resolveUrl({\n url: content,\n base: source.target,\n });\n const symLinkUrlResponse = await reader.readUrl(symLinkUrl);\n content = (await symLinkUrlResponse.buffer()).toString('utf-8');\n }\n\n cache.set(entityRef, {\n name: fileType.name,\n type: fileType.type,\n content: content,\n });\n logger.info(\n `Found README for ${entityRef}: ${url} type ${fileType.type}`,\n );\n response.send(content);\n return;\n } catch (error: unknown) {\n if (isError(error) && error.name === 'NotFoundError') {\n continue;\n } else {\n response.status(500).json({\n error: `Readme failure: ${error}`,\n });\n return;\n }\n }\n }\n logger.info(`Readme not found for ${entityRef}`);\n cache.set(entityRef, {\n name: NOT_FOUND_PLACEHOLDER,\n type: '',\n content: '',\n });\n throw new NotFoundError('Readme could not be found');\n });\n\n const middleware = MiddlewareFactory.create({ logger, config });\n router.use(middleware.error());\n return router;\n}\n"],"names":["catalogClient","CatalogClient","ScmIntegrations","Router","express","stringifyEntityRef","NOT_FOUND_PLACEHOLDER","NotFoundError","getEntitySourceLocation","getReadmeTypes","isSymLink","isError","MiddlewareFactory"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-backstage/plugin-readme-backend",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -16,7 +16,10 @@
16
16
  "pluginPackages": [
17
17
  "@axis-backstage/plugin-readme",
18
18
  "@axis-backstage/plugin-readme-backend"
19
- ]
19
+ ],
20
+ "features": {
21
+ ".": "@backstage/BackendFeature"
22
+ }
20
23
  },
21
24
  "scripts": {
22
25
  "start": "backstage-cli package start",
@@ -28,25 +31,35 @@
28
31
  "postpack": "backstage-cli package postpack"
29
32
  },
30
33
  "dependencies": {
31
- "@backstage/backend-defaults": "^0.4.3",
32
- "@backstage/backend-plugin-api": "^0.8.0",
33
- "@backstage/catalog-client": "^1.6.6",
34
- "@backstage/catalog-model": "^1.6.0",
35
- "@backstage/errors": "^1.2.4",
36
- "@backstage/integration": "^1.14.0",
34
+ "@backstage/backend-defaults": "^0.8.1",
35
+ "@backstage/backend-plugin-api": "^1.2.0",
36
+ "@backstage/catalog-client": "^1.9.1",
37
+ "@backstage/catalog-model": "^1.7.3",
38
+ "@backstage/errors": "^1.2.7",
39
+ "@backstage/integration": "^1.16.1",
37
40
  "@types/express": "*",
38
41
  "express": "^4.17.1",
39
42
  "express-promise-router": "^4.1.0"
40
43
  },
41
44
  "devDependencies": {
42
- "@backstage/backend-test-utils": "^0.5.0",
43
- "@backstage/cli": "^0.27.0",
45
+ "@backstage/backend-test-utils": "^1.3.0",
46
+ "@backstage/cli": "^0.30.0",
47
+ "@backstage/config": "^1.3.2",
44
48
  "@types/supertest": "^2.0.12",
45
- "msw": "^1.0.0",
49
+ "msw": "^2.7.3",
46
50
  "supertest": "^6.2.4"
47
51
  },
48
52
  "files": [
49
- "dist"
53
+ "dist",
54
+ "config.d.ts"
50
55
  ],
56
+ "configSchema": "config.d.ts",
57
+ "typesVersions": {
58
+ "*": {
59
+ "index": [
60
+ "dist/index.d.ts"
61
+ ]
62
+ }
63
+ },
51
64
  "directory": "_release/package"
52
65
  }