@axis-backstage/plugin-readme 0.11.0 → 0.12.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/README.md CHANGED
@@ -48,6 +48,24 @@ const overviewContent = (
48
48
  )
49
49
  ```
50
50
 
51
+ If you wish to only render the ReadmeCard if a README file can be found for the entity, you can use the exported function **isReadmeAvailable**. See example below:
52
+
53
+ ```tsx
54
+ import { ReadmeCard, isReadmeAvailable } from '@axis-backstage/plugin-readme';
55
+
56
+ const defaultEntityPage = (
57
+ ...
58
+ <EntitySwitch>
59
+ <EntitySwitch.Case if={isReadmeAvailable}>
60
+ <Grid md={6} xs={12}>
61
+ <ReadmeCard maxHeight={350} />
62
+ </Grid>
63
+ </EntitySwitch.Case>
64
+ </EntitySwitch>
65
+ ...
66
+ )
67
+ ```
68
+
51
69
  To use `ReadmeCard` in a seperate page with full height:
52
70
 
53
71
  ```tsx
@@ -59,6 +77,7 @@ const defaultEntityPage = (
59
77
  <ReadmeCard variant="fullHeight" />
60
78
  </EntityLayout.Route>
61
79
  ...
80
+ )
62
81
  ```
63
82
 
64
83
  ## Layout
@@ -1,5 +1,21 @@
1
- import { parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
1
+ import { stringifyEntityRef, parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
2
+ import { ResponseError } from '@backstage/errors';
3
+ import { readmeApiRef } from './ReadmeApi.esm.js';
2
4
 
5
+ const isReadmeAvailable = async (entity, context) => {
6
+ const readmeClient = context.apis.get(readmeApiRef);
7
+ if (readmeClient === void 0) {
8
+ return false;
9
+ }
10
+ try {
11
+ await readmeClient.getReadmeContent(stringifyEntityRef(entity));
12
+ } catch (error) {
13
+ if (error instanceof ResponseError && error.statusCode === 404) {
14
+ return false;
15
+ }
16
+ }
17
+ return true;
18
+ };
3
19
  class ReadmeClient {
4
20
  discoveryApi;
5
21
  fetchApi;
@@ -28,12 +44,9 @@ class ReadmeClient {
28
44
  resp.headers.get("Content-Type") || "text/plain"
29
45
  ];
30
46
  }
31
- if (resp.status === 404) {
32
- throw new Error("404");
33
- }
34
- throw new Error(`${resp.status}: ${resp.statusText}`);
47
+ throw await ResponseError.fromResponse(resp);
35
48
  }
36
49
  }
37
50
 
38
- export { ReadmeClient };
51
+ export { ReadmeClient, isReadmeAvailable };
39
52
  //# sourceMappingURL=ReadmeClient.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReadmeClient.esm.js","sources":["../../src/api/ReadmeClient.tsx"],"sourcesContent":["import {\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model';\nimport { ReadmeApi } from './ReadmeApi';\n\nexport class ReadmeClient implements ReadmeApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly identityApi: IdentityApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n identityApi: IdentityApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.identityApi = options.identityApi;\n }\n\n async getReadmeContent(entityRef: string): Promise<[string, string]> {\n const { token } = await this.identityApi.getCredentials();\n const baseUrl = await this.discoveryApi.getBaseUrl('readme');\n\n const { kind, name, namespace } = parseEntityRef(entityRef, {\n defaultNamespace: DEFAULT_NAMESPACE,\n });\n\n const resp = await this.fetchApi.fetch(\n `${baseUrl}/${kind}/${namespace}/${name}`,\n {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n if (resp.ok) {\n return [\n await resp.text(),\n resp.headers.get('Content-Type') || 'text/plain',\n ];\n }\n if (resp.status === 404) {\n throw new Error('404');\n }\n throw new Error(`${resp.status}: ${resp.statusText}`);\n }\n}\n"],"names":[],"mappings":";;AAQO,MAAM,YAAkC,CAAA;AAAA,EAC5B,YAAA,CAAA;AAAA,EACA,QAAA,CAAA;AAAA,EACA,WAAA,CAAA;AAAA,EAEjB,YAAY,OAIT,EAAA;AACD,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA,CAAA;AACxB,IAAA,IAAA,CAAK,cAAc,OAAQ,CAAA,WAAA,CAAA;AAAA,GAC7B;AAAA,EAEA,MAAM,iBAAiB,SAA8C,EAAA;AACnE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA,CAAA;AACxD,IAAA,MAAM,OAAU,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,QAAQ,CAAA,CAAA;AAE3D,IAAA,MAAM,EAAE,IAAM,EAAA,IAAA,EAAM,SAAU,EAAA,GAAI,eAAe,SAAW,EAAA;AAAA,MAC1D,gBAAkB,EAAA,iBAAA;AAAA,KACnB,CAAA,CAAA;AAED,IAAM,MAAA,IAAA,GAAO,MAAM,IAAA,CAAK,QAAS,CAAA,KAAA;AAAA,MAC/B,GAAG,OAAO,CAAA,CAAA,EAAI,IAAI,CAAI,CAAA,EAAA,SAAS,IAAI,IAAI,CAAA,CAAA;AAAA,MACvC;AAAA,QACE,MAAQ,EAAA,KAAA;AAAA,QACR,OAAS,EAAA,EAAE,aAAe,EAAA,CAAA,OAAA,EAAU,KAAK,CAAG,CAAA,EAAA;AAAA,OAC9C;AAAA,KACF,CAAA;AACA,IAAA,IAAI,KAAK,EAAI,EAAA;AACX,MAAO,OAAA;AAAA,QACL,MAAM,KAAK,IAAK,EAAA;AAAA,QAChB,IAAK,CAAA,OAAA,CAAQ,GAAI,CAAA,cAAc,CAAK,IAAA,YAAA;AAAA,OACtC,CAAA;AAAA,KACF;AACA,IAAI,IAAA,IAAA,CAAK,WAAW,GAAK,EAAA;AACvB,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA,CAAA;AAAA,KACvB;AACA,IAAM,MAAA,IAAI,MAAM,CAAG,EAAA,IAAA,CAAK,MAAM,CAAK,EAAA,EAAA,IAAA,CAAK,UAAU,CAAE,CAAA,CAAA,CAAA;AAAA,GACtD;AACF;;;;"}
1
+ {"version":3,"file":"ReadmeClient.esm.js","sources":["../../src/api/ReadmeClient.tsx"],"sourcesContent":["import {\n ApiHolder,\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport {\n DEFAULT_NAMESPACE,\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { ResponseError } from '@backstage/errors';\nimport { ReadmeApi, readmeApiRef } from './ReadmeApi';\n\n/**\n * Checks if a README is available for the given entity by making a request to the backend.\nIf the backend returns a 404 NotFound error, it indicates that no README is available for the entity.\n@param entity - The entity for which to check the README availability.\n@param context - The context providing access to the API holder.\n@returns A promise that resolves to true if the README is available, or false if not found (404).\n * @public\n */\nexport const isReadmeAvailable = async (\n entity: Entity,\n context: { apis: ApiHolder },\n): Promise<boolean> => {\n const readmeClient = context.apis.get(readmeApiRef);\n\n if (readmeClient === undefined) {\n return false;\n }\n\n try {\n await readmeClient.getReadmeContent(stringifyEntityRef(entity));\n } catch (error) {\n if (error instanceof ResponseError && error.statusCode === 404) {\n return false;\n }\n }\n return true;\n};\n\nexport class ReadmeClient implements ReadmeApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly identityApi: IdentityApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n identityApi: IdentityApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.identityApi = options.identityApi;\n }\n\n async getReadmeContent(entityRef: string): Promise<[string, string]> {\n const { token } = await this.identityApi.getCredentials();\n const baseUrl = await this.discoveryApi.getBaseUrl('readme');\n\n const { kind, name, namespace } = parseEntityRef(entityRef, {\n defaultNamespace: DEFAULT_NAMESPACE,\n });\n\n const resp = await this.fetchApi.fetch(\n `${baseUrl}/${kind}/${namespace}/${name}`,\n {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n\n if (resp.ok) {\n return [\n await resp.text(),\n resp.headers.get('Content-Type') || 'text/plain',\n ];\n }\n\n throw await ResponseError.fromResponse(resp);\n }\n}\n"],"names":[],"mappings":";;;;AAuBa,MAAA,iBAAA,GAAoB,OAC/B,MAAA,EACA,OACqB,KAAA;AACrB,EAAA,MAAM,YAAe,GAAA,OAAA,CAAQ,IAAK,CAAA,GAAA,CAAI,YAAY,CAAA,CAAA;AAElD,EAAA,IAAI,iBAAiB,KAAW,CAAA,EAAA;AAC9B,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AAEA,EAAI,IAAA;AACF,IAAA,MAAM,YAAa,CAAA,gBAAA,CAAiB,kBAAmB,CAAA,MAAM,CAAC,CAAA,CAAA;AAAA,WACvD,KAAO,EAAA;AACd,IAAA,IAAI,KAAiB,YAAA,aAAA,IAAiB,KAAM,CAAA,UAAA,KAAe,GAAK,EAAA;AAC9D,MAAO,OAAA,KAAA,CAAA;AAAA,KACT;AAAA,GACF;AACA,EAAO,OAAA,IAAA,CAAA;AACT,EAAA;AAEO,MAAM,YAAkC,CAAA;AAAA,EAC5B,YAAA,CAAA;AAAA,EACA,QAAA,CAAA;AAAA,EACA,WAAA,CAAA;AAAA,EAEjB,YAAY,OAIT,EAAA;AACD,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA,CAAA;AACxB,IAAA,IAAA,CAAK,cAAc,OAAQ,CAAA,WAAA,CAAA;AAAA,GAC7B;AAAA,EAEA,MAAM,iBAAiB,SAA8C,EAAA;AACnE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA,CAAA;AACxD,IAAA,MAAM,OAAU,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,QAAQ,CAAA,CAAA;AAE3D,IAAA,MAAM,EAAE,IAAM,EAAA,IAAA,EAAM,SAAU,EAAA,GAAI,eAAe,SAAW,EAAA;AAAA,MAC1D,gBAAkB,EAAA,iBAAA;AAAA,KACnB,CAAA,CAAA;AAED,IAAM,MAAA,IAAA,GAAO,MAAM,IAAA,CAAK,QAAS,CAAA,KAAA;AAAA,MAC/B,GAAG,OAAO,CAAA,CAAA,EAAI,IAAI,CAAI,CAAA,EAAA,SAAS,IAAI,IAAI,CAAA,CAAA;AAAA,MACvC;AAAA,QACE,MAAQ,EAAA,KAAA;AAAA,QACR,OAAS,EAAA,EAAE,aAAe,EAAA,CAAA,OAAA,EAAU,KAAK,CAAG,CAAA,EAAA;AAAA,OAC9C;AAAA,KACF,CAAA;AAEA,IAAA,IAAI,KAAK,EAAI,EAAA;AACX,MAAO,OAAA;AAAA,QACL,MAAM,KAAK,IAAK,EAAA;AAAA,QAChB,IAAK,CAAA,OAAA,CAAQ,GAAI,CAAA,cAAc,CAAK,IAAA,YAAA;AAAA,OACtC,CAAA;AAAA,KACF;AAEA,IAAM,MAAA,MAAM,aAAc,CAAA,YAAA,CAAa,IAAI,CAAA,CAAA;AAAA,GAC7C;AACF;;;;"}
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
2
  import { useEntity } from '@backstage/plugin-catalog-react';
3
3
  import { useApi } from '@backstage/core-plugin-api';
4
- import { Progress, Link, ErrorPanel, MarkdownContent, CodeSnippet } from '@backstage/core-components';
4
+ import { Progress, Link, ResponseErrorPanel, ErrorPanel, MarkdownContent, CodeSnippet } from '@backstage/core-components';
5
5
  import useAsync from 'react-use/lib/useAsync';
6
6
  import { readmeApiRef } from '../../api/ReadmeApi.esm.js';
7
7
  import { getEntitySourceLocation, stringifyEntityRef } from '@backstage/catalog-model';
8
8
  import Box from '@mui/material/Box';
9
9
  import Typography from '@mui/material/Typography';
10
+ import { ResponseError } from '@backstage/errors';
10
11
 
11
12
  const FetchComponent = () => {
12
13
  const { entity } = useEntity();
@@ -29,8 +30,11 @@ const FetchComponent = () => {
29
30
  if (loading) {
30
31
  return /* @__PURE__ */ React.createElement(Progress, null);
31
32
  }
32
- if (error?.message === "404") {
33
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { pb: 2, variant: "body2" }, "No README.md file found at source location:", " ", location && /* @__PURE__ */ React.createElement("strong", null, location)), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Need help? Go to our", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md" }, "documentation")));
33
+ if (error instanceof ResponseError) {
34
+ if (error.statusCode === 404) {
35
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { pb: 2, variant: "body2" }, "No README.md file found at source location:", " ", location && /* @__PURE__ */ React.createElement("strong", null, location)), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Need help? Go to our", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md" }, "documentation")));
36
+ }
37
+ return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
34
38
  }
35
39
  if (error) {
36
40
  return /* @__PURE__ */ React.createElement(ErrorPanel, { error });
@@ -1 +1 @@
1
- {"version":3,"file":"FetchComponent.esm.js","sources":["../../../src/components/FetchComponent/FetchComponent.tsx"],"sourcesContent":["import React from 'react';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n CodeSnippet,\n ErrorPanel,\n Link,\n MarkdownContent,\n Progress,\n} from '@backstage/core-components';\nimport useAsync from 'react-use/lib/useAsync';\nimport { readmeApiRef } from '../../api/ReadmeApi';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { getEntitySourceLocation } from '@backstage/catalog-model';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\n\nexport const FetchComponent = () => {\n const { entity } = useEntity();\n const readmeApi = useApi(readmeApiRef);\n let location;\n\n try {\n const { target } = getEntitySourceLocation(entity);\n location = target ?? target;\n } catch (err) {\n location = ': Unknown';\n }\n\n const {\n value: content,\n loading,\n error,\n } = useAsync(\n async (): Promise<string[]> =>\n await readmeApi.getReadmeContent(stringifyEntityRef(entity)),\n [entity],\n );\n\n if (loading) {\n return <Progress />;\n }\n if (error?.message === '404') {\n return (\n <Box>\n <Typography pb={2} variant=\"body2\">\n No README.md file found at source location:{' '}\n {location && <strong>{location}</strong>}\n </Typography>\n <Typography variant=\"body2\">\n Need help? Go to our{' '}\n <Link to=\"https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md\">\n documentation\n </Link>\n </Typography>\n </Box>\n );\n }\n\n if (error) {\n return <ErrorPanel error={error} />;\n }\n if (!content) {\n return <ErrorPanel error={Error('Unknown error')} />;\n }\n if (!content[1] || content[1] === 'error') {\n return <ErrorPanel error={Error(content[1] ?? 'Unknown error')} />;\n } else if (content[1].startsWith('text/markdown')) {\n return <MarkdownContent content={content[0]} />;\n }\n // probably text/plain\n return (\n <div data-testid=\"readme-content\">\n <CodeSnippet text={content[0]} language=\"plaintext\" />\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;AAiBO,MAAM,iBAAiB,MAAM;AAClC,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA,CAAA;AAC7B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA,CAAA;AACrC,EAAI,IAAA,QAAA,CAAA;AAEJ,EAAI,IAAA;AACF,IAAA,MAAM,EAAE,MAAA,EAAW,GAAA,uBAAA,CAAwB,MAAM,CAAA,CAAA;AACjD,IAAA,QAAA,GAAW,MAAU,IAAA,MAAA,CAAA;AAAA,WACd,GAAK,EAAA;AACZ,IAAW,QAAA,GAAA,WAAA,CAAA;AAAA,GACb;AAEA,EAAM,MAAA;AAAA,IACJ,KAAO,EAAA,OAAA;AAAA,IACP,OAAA;AAAA,IACA,KAAA;AAAA,GACE,GAAA,QAAA;AAAA,IACF,YACE,MAAM,SAAA,CAAU,gBAAiB,CAAA,kBAAA,CAAmB,MAAM,CAAC,CAAA;AAAA,IAC7D,CAAC,MAAM,CAAA;AAAA,GACT,CAAA;AAEA,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,2CAAQ,QAAS,EAAA,IAAA,CAAA,CAAA;AAAA,GACnB;AACA,EAAI,IAAA,KAAA,EAAO,YAAY,KAAO,EAAA;AAC5B,IAAA,uBACG,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,EAAI,EAAA,CAAA,EAAG,OAAQ,EAAA,OAAA,EAAA,EAAQ,6CACW,EAAA,GAAA,EAC3C,QAAY,oBAAA,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAA,IAAA,EAAA,QAAS,CACjC,CAAA,kBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,OAAA,EAAA,EAAQ,sBACL,EAAA,GAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,EAAG,EAAA,4FAAA,EAAA,EAA6F,eAEtG,CACF,CACF,CAAA,CAAA;AAAA,GAEJ;AAEA,EAAA,IAAI,KAAO,EAAA;AACT,IAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAc,EAAA,CAAA,CAAA;AAAA,GACnC;AACA,EAAA,IAAI,CAAC,OAAS,EAAA;AACZ,IAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,KAAO,EAAA,KAAA,CAAM,eAAe,CAAG,EAAA,CAAA,CAAA;AAAA,GACpD;AACA,EAAA,IAAI,CAAC,OAAQ,CAAA,CAAC,KAAK,OAAQ,CAAA,CAAC,MAAM,OAAS,EAAA;AACzC,IAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAO,EAAA,KAAA,CAAM,QAAQ,CAAC,CAAA,IAAK,eAAe,CAAG,EAAA,CAAA,CAAA;AAAA,aACvD,OAAQ,CAAA,CAAC,CAAE,CAAA,UAAA,CAAW,eAAe,CAAG,EAAA;AACjD,IAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,eAAA,EAAA,EAAgB,OAAS,EAAA,OAAA,CAAQ,CAAC,CAAG,EAAA,CAAA,CAAA;AAAA,GAC/C;AAEA,EAAA,uBACG,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAI,aAAY,EAAA,gBAAA,EAAA,kBACd,KAAA,CAAA,aAAA,CAAA,WAAA,EAAA,EAAY,IAAM,EAAA,OAAA,CAAQ,CAAC,CAAA,EAAG,QAAS,EAAA,WAAA,EAAY,CACtD,CAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"FetchComponent.esm.js","sources":["../../../src/components/FetchComponent/FetchComponent.tsx"],"sourcesContent":["import React from 'react';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n CodeSnippet,\n ErrorPanel,\n Link,\n MarkdownContent,\n Progress,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport useAsync from 'react-use/lib/useAsync';\nimport { readmeApiRef } from '../../api/ReadmeApi';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { getEntitySourceLocation } from '@backstage/catalog-model';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport { ResponseError } from '@backstage/errors';\n\nexport const FetchComponent = () => {\n const { entity } = useEntity();\n const readmeApi = useApi(readmeApiRef);\n let location;\n\n try {\n const { target } = getEntitySourceLocation(entity);\n location = target ?? target;\n } catch (err) {\n location = ': Unknown';\n }\n\n const {\n value: content,\n loading,\n error,\n } = useAsync(\n async (): Promise<string[]> =>\n await readmeApi.getReadmeContent(stringifyEntityRef(entity)),\n [entity],\n );\n\n if (loading) {\n return <Progress />;\n }\n\n if (error instanceof ResponseError) {\n if (error.statusCode === 404) {\n return (\n <Box>\n <Typography pb={2} variant=\"body2\">\n No README.md file found at source location:{' '}\n {location && <strong>{location}</strong>}\n </Typography>\n <Typography variant=\"body2\">\n Need help? Go to our{' '}\n <Link to=\"https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md\">\n documentation\n </Link>\n </Typography>\n </Box>\n );\n }\n return <ResponseErrorPanel error={error} />;\n }\n\n if (error) {\n return <ErrorPanel error={error} />;\n }\n if (!content) {\n return <ErrorPanel error={Error('Unknown error')} />;\n }\n if (!content[1] || content[1] === 'error') {\n return <ErrorPanel error={Error(content[1] ?? 'Unknown error')} />;\n } else if (content[1].startsWith('text/markdown')) {\n return <MarkdownContent content={content[0]} />;\n }\n // probably text/plain\n return (\n <div data-testid=\"readme-content\">\n <CodeSnippet text={content[0]} language=\"plaintext\" />\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AAmBO,MAAM,iBAAiB,MAAM;AAClC,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA,CAAA;AAC7B,EAAM,MAAA,SAAA,GAAY,OAAO,YAAY,CAAA,CAAA;AACrC,EAAI,IAAA,QAAA,CAAA;AAEJ,EAAI,IAAA;AACF,IAAA,MAAM,EAAE,MAAA,EAAW,GAAA,uBAAA,CAAwB,MAAM,CAAA,CAAA;AACjD,IAAA,QAAA,GAAW,MAAU,IAAA,MAAA,CAAA;AAAA,WACd,GAAK,EAAA;AACZ,IAAW,QAAA,GAAA,WAAA,CAAA;AAAA,GACb;AAEA,EAAM,MAAA;AAAA,IACJ,KAAO,EAAA,OAAA;AAAA,IACP,OAAA;AAAA,IACA,KAAA;AAAA,GACE,GAAA,QAAA;AAAA,IACF,YACE,MAAM,SAAA,CAAU,gBAAiB,CAAA,kBAAA,CAAmB,MAAM,CAAC,CAAA;AAAA,IAC7D,CAAC,MAAM,CAAA;AAAA,GACT,CAAA;AAEA,EAAA,IAAI,OAAS,EAAA;AACX,IAAA,2CAAQ,QAAS,EAAA,IAAA,CAAA,CAAA;AAAA,GACnB;AAEA,EAAA,IAAI,iBAAiB,aAAe,EAAA;AAClC,IAAI,IAAA,KAAA,CAAM,eAAe,GAAK,EAAA;AAC5B,MAAA,uBACG,KAAA,CAAA,aAAA,CAAA,GAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,EAAI,EAAA,CAAA,EAAG,OAAQ,EAAA,OAAA,EAAA,EAAQ,6CACW,EAAA,GAAA,EAC3C,QAAY,oBAAA,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAA,IAAA,EAAA,QAAS,CACjC,CAAA,kBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,OAAA,EAAA,EAAQ,sBACL,EAAA,GAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,EAAG,EAAA,4FAAA,EAAA,EAA6F,eAEtG,CACF,CACF,CAAA,CAAA;AAAA,KAEJ;AACA,IAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,sBAAmB,KAAc,EAAA,CAAA,CAAA;AAAA,GAC3C;AAEA,EAAA,IAAI,KAAO,EAAA;AACT,IAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAc,EAAA,CAAA,CAAA;AAAA,GACnC;AACA,EAAA,IAAI,CAAC,OAAS,EAAA;AACZ,IAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,KAAO,EAAA,KAAA,CAAM,eAAe,CAAG,EAAA,CAAA,CAAA;AAAA,GACpD;AACA,EAAA,IAAI,CAAC,OAAQ,CAAA,CAAC,KAAK,OAAQ,CAAA,CAAC,MAAM,OAAS,EAAA;AACzC,IAAO,uBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAO,EAAA,KAAA,CAAM,QAAQ,CAAC,CAAA,IAAK,eAAe,CAAG,EAAA,CAAA,CAAA;AAAA,aACvD,OAAQ,CAAA,CAAC,CAAE,CAAA,UAAA,CAAW,eAAe,CAAG,EAAA;AACjD,IAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,eAAA,EAAA,EAAgB,OAAS,EAAA,OAAA,CAAQ,CAAC,CAAG,EAAA,CAAA,CAAA;AAAA,GAC/C;AAEA,EAAA,uBACG,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAI,aAAY,EAAA,gBAAA,EAAA,kBACd,KAAA,CAAA,aAAA,CAAA,WAAA,EAAA,EAAY,IAAM,EAAA,OAAA,CAAQ,CAAC,CAAA,EAAG,QAAS,EAAA,WAAA,EAAY,CACtD,CAAA,CAAA;AAEJ;;;;"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  import * as React from 'react';
3
3
  import { InfoCardVariants } from '@backstage/core-components';
4
4
  import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
5
+ import { ApiHolder } from '@backstage/core-plugin-api';
6
+ import { Entity } from '@backstage/catalog-model';
5
7
 
6
8
  /**
7
9
  * ReadmeCardProps props.
@@ -24,4 +26,16 @@ declare const readmePlugin: _backstage_core_plugin_api.BackstagePlugin<{
24
26
  * @public */
25
27
  declare const ReadmeCard: (props: ReadmeCardProps) => React.JSX.Element;
26
28
 
27
- export { ReadmeCard, type ReadmeCardProps, readmePlugin };
29
+ /**
30
+ * Checks if a README is available for the given entity by making a request to the backend.
31
+ If the backend returns a 404 NotFound error, it indicates that no README is available for the entity.
32
+ @param entity - The entity for which to check the README availability.
33
+ @param context - The context providing access to the API holder.
34
+ @returns A promise that resolves to true if the README is available, or false if not found (404).
35
+ * @public
36
+ */
37
+ declare const isReadmeAvailable: (entity: Entity, context: {
38
+ apis: ApiHolder;
39
+ }) => Promise<boolean>;
40
+
41
+ export { ReadmeCard, type ReadmeCardProps, isReadmeAvailable, readmePlugin };
package/dist/index.esm.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { ReadmeCard, readmePlugin } from './plugin.esm.js';
2
+ export { isReadmeAvailable } from './api/ReadmeClient.esm.js';
2
3
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-backstage/plugin-readme",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -32,6 +32,7 @@
32
32
  "@backstage/catalog-model": "^1.6.0",
33
33
  "@backstage/core-components": "^0.14.10",
34
34
  "@backstage/core-plugin-api": "^1.9.3",
35
+ "@backstage/errors": "^1.2.4",
35
36
  "@backstage/plugin-catalog-react": "^1.12.3",
36
37
  "@backstage/theme": "^0.5.6",
37
38
  "@mui/icons-material": "^5.15.7",