@rwdocs/backstage-plugin-rw-backend 0.1.2 → 0.1.4

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 CHANGED
@@ -13,6 +13,17 @@ export interface Config {
13
13
  entity?: string;
14
14
  /** Maximum number of cached RwSite instances. Default: 20. */
15
15
  cacheSize?: number;
16
+ /**
17
+ * How often to check cached sites for upstream changes and reload if needed.
18
+ * If not set, no periodic reloading is performed.
19
+ *
20
+ * @example
21
+ * ```yaml
22
+ * rw:
23
+ * reloadInterval: { minutes: 5 }
24
+ * ```
25
+ */
26
+ reloadInterval?: import("@backstage/types").HumanDuration;
16
27
  /**
17
28
  * S3 storage configuration. Shared across all entity sites.
18
29
  * Mutually exclusive with `projectDir`.
package/dist/hub.cjs.js CHANGED
@@ -33,6 +33,19 @@ class Hub {
33
33
  this.cache.set(entityRef, site);
34
34
  return site;
35
35
  }
36
+ async reloadAll(logger) {
37
+ const entries = [...this.cache.entries()];
38
+ for (const [ref, site] of entries) {
39
+ try {
40
+ const reloaded = await site.reload();
41
+ if (reloaded) {
42
+ logger.info(`Reloaded site: ${ref}`);
43
+ }
44
+ } catch (err) {
45
+ logger.warn(`Failed to reload site ${ref}: ${err}`);
46
+ }
47
+ }
48
+ }
36
49
  getS3Site(entityRef) {
37
50
  const cached = this.cache.get(entityRef);
38
51
  if (cached) {
@@ -1 +1 @@
1
- {"version":3,"file":"hub.cjs.js","sources":["../src/hub.ts"],"sourcesContent":["import { createSite, type RwSite, type SiteConfig, type DiagramsConfig } from \"@rwdocs/core\";\nimport { toEntityPath } from \"./entityPath\";\n\nexport interface S3SharedConfig {\n bucket: string;\n region?: string;\n endpoint?: string;\n bucketRootPath?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n}\n\nexport interface HubOptions {\n s3?: S3SharedConfig;\n projectDir?: string;\n /** Entity ref in any format accepted by parseEntityRef. Normalized internally. */\n entity?: string;\n diagrams?: DiagramsConfig;\n cacheSize?: number;\n}\n\nexport class Hub {\n private readonly options: HubOptions;\n private readonly cache: Map<string, RwSite> = new Map();\n private readonly maxSize: number;\n\n constructor(options: HubOptions) {\n this.options = {\n ...options,\n entity: options.entity ? toEntityPath(options.entity) : undefined,\n };\n this.maxSize = options.cacheSize ?? 20;\n }\n\n getSite(entityRef: string): RwSite | undefined {\n if (this.options.projectDir) {\n return this.getLocalSite(entityRef);\n }\n return this.getS3Site(entityRef);\n }\n\n private getLocalSite(entityRef: string): RwSite | undefined {\n if (entityRef !== this.options.entity) {\n return undefined;\n }\n\n const cached = this.cache.get(entityRef);\n if (cached) return cached;\n\n const site = createSite({\n projectDir: this.options.projectDir,\n diagrams: this.options.diagrams,\n });\n this.cache.set(entityRef, site);\n return site;\n }\n\n private getS3Site(entityRef: string): RwSite {\n const cached = this.cache.get(entityRef);\n if (cached) {\n this.cache.delete(entityRef);\n this.cache.set(entityRef, cached);\n return cached;\n }\n\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value!;\n this.cache.delete(firstKey);\n }\n\n const s3 = this.options.s3!;\n const config: SiteConfig = {\n s3: { ...s3, entity: entityRef },\n diagrams: this.options.diagrams,\n };\n\n const site = createSite(config);\n this.cache.set(entityRef, site);\n return site;\n }\n}\n"],"names":["toEntityPath","createSite"],"mappings":";;;;;AAqBO,MAAM,GAAA,CAAI;AAAA,EACE,OAAA;AAAA,EACA,KAAA,uBAAiC,GAAA,EAAI;AAAA,EACrC,OAAA;AAAA,EAEjB,YAAY,OAAA,EAAqB;AAC/B,IAAA,IAAA,CAAK,OAAA,GAAU;AAAA,MACb,GAAG,OAAA;AAAA,MACH,QAAQ,OAAA,CAAQ,MAAA,GAASA,uBAAA,CAAa,OAAA,CAAQ,MAAM,CAAA,GAAI;AAAA,KAC1D;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,QAAQ,SAAA,IAAa,EAAA;AAAA,EACtC;AAAA,EAEA,QAAQ,SAAA,EAAuC;AAC7C,IAAA,IAAI,IAAA,CAAK,QAAQ,UAAA,EAAY;AAC3B,MAAA,OAAO,IAAA,CAAK,aAAa,SAAS,CAAA;AAAA,IACpC;AACA,IAAA,OAAO,IAAA,CAAK,UAAU,SAAS,CAAA;AAAA,EACjC;AAAA,EAEQ,aAAa,SAAA,EAAuC;AAC1D,IAAA,IAAI,SAAA,KAAc,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ;AACrC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AACvC,IAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,IAAA,MAAM,OAAOC,eAAA,CAAW;AAAA,MACtB,UAAA,EAAY,KAAK,OAAA,CAAQ,UAAA;AAAA,MACzB,QAAA,EAAU,KAAK,OAAA,CAAQ;AAAA,KACxB,CAAA;AACD,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,SAAA,EAAW,IAAI,CAAA;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,UAAU,SAAA,EAA2B;AAC3C,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AACvC,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,SAAS,CAAA;AAC3B,MAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,SAAA,EAAW,MAAM,CAAA;AAChC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,IAAA,IAAQ,IAAA,CAAK,OAAA,EAAS;AACnC,MAAA,MAAM,WAAW,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AAC1C,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,QAAQ,CAAA;AAAA,IAC5B;AAEA,IAAA,MAAM,EAAA,GAAK,KAAK,OAAA,CAAQ,EAAA;AACxB,IAAA,MAAM,MAAA,GAAqB;AAAA,MACzB,EAAA,EAAI,EAAE,GAAG,EAAA,EAAI,QAAQ,SAAA,EAAU;AAAA,MAC/B,QAAA,EAAU,KAAK,OAAA,CAAQ;AAAA,KACzB;AAEA,IAAA,MAAM,IAAA,GAAOA,gBAAW,MAAM,CAAA;AAC9B,IAAA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,SAAA,EAAW,IAAI,CAAA;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;;"}
1
+ {"version":3,"file":"hub.cjs.js","sources":["../src/hub.ts"],"sourcesContent":["import type { LoggerService } from \"@backstage/backend-plugin-api\";\nimport { createSite, type RwSite, type SiteConfig, type DiagramsConfig } from \"@rwdocs/core\";\nimport { toEntityPath } from \"./entityPath\";\n\nexport interface S3SharedConfig {\n bucket: string;\n region?: string;\n endpoint?: string;\n bucketRootPath?: string;\n accessKeyId?: string;\n secretAccessKey?: string;\n}\n\nexport interface HubOptions {\n s3?: S3SharedConfig;\n projectDir?: string;\n /** Entity ref in any format accepted by parseEntityRef. Normalized internally. */\n entity?: string;\n diagrams?: DiagramsConfig;\n cacheSize?: number;\n}\n\nexport class Hub {\n private readonly options: HubOptions;\n private readonly cache: Map<string, RwSite> = new Map();\n private readonly maxSize: number;\n\n constructor(options: HubOptions) {\n this.options = {\n ...options,\n entity: options.entity ? toEntityPath(options.entity) : undefined,\n };\n this.maxSize = options.cacheSize ?? 20;\n }\n\n getSite(entityRef: string): RwSite | undefined {\n if (this.options.projectDir) {\n return this.getLocalSite(entityRef);\n }\n return this.getS3Site(entityRef);\n }\n\n private getLocalSite(entityRef: string): RwSite | undefined {\n if (entityRef !== this.options.entity) {\n return undefined;\n }\n\n const cached = this.cache.get(entityRef);\n if (cached) return cached;\n\n const site = createSite({\n projectDir: this.options.projectDir,\n diagrams: this.options.diagrams,\n });\n this.cache.set(entityRef, site);\n return site;\n }\n\n async reloadAll(logger: LoggerService) {\n const entries = [...this.cache.entries()];\n for (const [ref, site] of entries) {\n try {\n const reloaded = await site.reload();\n if (reloaded) {\n logger.info(`Reloaded site: ${ref}`);\n }\n } catch (err) {\n logger.warn(`Failed to reload site ${ref}: ${err}`);\n }\n }\n }\n\n private getS3Site(entityRef: string): RwSite {\n const cached = this.cache.get(entityRef);\n if (cached) {\n this.cache.delete(entityRef);\n this.cache.set(entityRef, cached);\n return cached;\n }\n\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value!;\n this.cache.delete(firstKey);\n }\n\n const s3 = this.options.s3!;\n const config: SiteConfig = {\n s3: { ...s3, entity: entityRef },\n diagrams: this.options.diagrams,\n };\n\n const site = createSite(config);\n this.cache.set(entityRef, site);\n return site;\n }\n}\n"],"names":["toEntityPath","createSite"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var config = require('@backstage/config');
4
5
  var router = require('./router.cjs.js');
5
6
  var hub = require('./hub.cjs.js');
6
7
  var entityPath = require('./entityPath.cjs.js');
@@ -13,13 +14,14 @@ const rwPlugin = backendPluginApi.createBackendPlugin({
13
14
  httpRouter: backendPluginApi.coreServices.httpRouter,
14
15
  httpAuth: backendPluginApi.coreServices.httpAuth,
15
16
  logger: backendPluginApi.coreServices.logger,
16
- config: backendPluginApi.coreServices.rootConfig
17
+ config: backendPluginApi.coreServices.rootConfig,
18
+ scheduler: backendPluginApi.coreServices.scheduler
17
19
  },
18
- async init({ httpRouter, httpAuth, logger, config }) {
19
- const projectDir = config.getOptionalString("rw.projectDir");
20
- const entity = config.getOptionalString("rw.entity");
21
- const cacheSize = config.getOptionalNumber("rw.cacheSize");
22
- const s3Config = config.getOptionalConfig("rw.s3");
20
+ async init({ httpRouter, httpAuth, logger, config: config$1, scheduler }) {
21
+ const projectDir = config$1.getOptionalString("rw.projectDir");
22
+ const entity = config$1.getOptionalString("rw.entity");
23
+ const cacheSize = config$1.getOptionalNumber("rw.cacheSize");
24
+ const s3Config = config$1.getOptionalConfig("rw.s3");
23
25
  const s3 = s3Config ? {
24
26
  bucket: s3Config.getString("bucket"),
25
27
  region: s3Config.getOptionalString("region"),
@@ -34,7 +36,7 @@ const rwPlugin = backendPluginApi.createBackendPlugin({
34
36
  if (projectDir && !entity) {
35
37
  throw new Error("rw.entity is required when rw.projectDir is set");
36
38
  }
37
- const diagramsConfig = config.getOptionalConfig("rw.diagrams");
39
+ const diagramsConfig = config$1.getOptionalConfig("rw.diagrams");
38
40
  const diagrams = diagramsConfig ? {
39
41
  krokiUrl: diagramsConfig.getOptionalString("krokiUrl"),
40
42
  dpi: diagramsConfig.getOptionalNumber("dpi")
@@ -54,6 +56,17 @@ const rwPlugin = backendPluginApi.createBackendPlugin({
54
56
  `Hub: local mode (${projectDir}, entity: ${entity ? entityPath.toEntityPath(entity) : entity})`
55
57
  );
56
58
  }
59
+ if (config$1.has("rw.reloadInterval")) {
60
+ const frequency = config.readDurationFromConfig(config$1, { key: "rw.reloadInterval" });
61
+ logger.info(`Scheduling site reload with interval: ${JSON.stringify(frequency)}`);
62
+ await scheduler.scheduleTask({
63
+ id: "rw-site-reload",
64
+ frequency,
65
+ timeout: frequency,
66
+ scope: "local",
67
+ fn: async () => hub$1.reloadAll(logger)
68
+ });
69
+ }
57
70
  const router$1 = await router.createRouter({ hub: hub$1 });
58
71
  httpRouter.use(router$1);
59
72
  httpRouter.addAuthPolicy({
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import { coreServices, createBackendPlugin } from \"@backstage/backend-plugin-api\";\nimport { createRouter } from \"./router\";\nimport { Hub, type HubOptions } from \"./hub\";\nimport { toEntityPath } from \"./entityPath\";\n\nexport const rwPlugin = createBackendPlugin({\n pluginId: \"rw\",\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n },\n async init({ httpRouter, httpAuth, logger, config }) {\n const projectDir = config.getOptionalString(\"rw.projectDir\");\n const entity = config.getOptionalString(\"rw.entity\");\n const cacheSize = config.getOptionalNumber(\"rw.cacheSize\");\n\n const s3Config = config.getOptionalConfig(\"rw.s3\");\n const s3 = s3Config\n ? {\n bucket: s3Config.getString(\"bucket\"),\n region: s3Config.getOptionalString(\"region\"),\n endpoint: s3Config.getOptionalString(\"endpoint\"),\n bucketRootPath: s3Config.getOptionalString(\"bucketRootPath\"),\n accessKeyId: s3Config.getOptionalString(\"accessKeyId\"),\n secretAccessKey: s3Config.getOptionalString(\"secretAccessKey\"),\n }\n : undefined;\n\n if (!projectDir && !s3) {\n throw new Error(\"Either rw.projectDir or rw.s3 must be configured\");\n }\n\n if (projectDir && !entity) {\n throw new Error(\"rw.entity is required when rw.projectDir is set\");\n }\n\n const diagramsConfig = config.getOptionalConfig(\"rw.diagrams\");\n const diagrams = diagramsConfig\n ? {\n krokiUrl: diagramsConfig.getOptionalString(\"krokiUrl\"),\n dpi: diagramsConfig.getOptionalNumber(\"dpi\"),\n }\n : undefined;\n\n const hubOptions: HubOptions = {\n projectDir,\n entity,\n s3,\n diagrams,\n cacheSize,\n };\n\n const hub = new Hub(hubOptions);\n\n if (s3) {\n logger.info(`Hub: S3 mode (bucket: ${s3.bucket}, cache size: ${cacheSize ?? 20})`);\n } else {\n logger.info(\n `Hub: local mode (${projectDir}, entity: ${entity ? toEntityPath(entity) : entity})`,\n );\n }\n\n const router = await createRouter({ logger, httpAuth, hub });\n httpRouter.use(router);\n httpRouter.addAuthPolicy({\n path: \"/health\",\n allow: \"unauthenticated\",\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","hub","Hub","toEntityPath","router","createRouter"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import { coreServices, createBackendPlugin } from \"@backstage/backend-plugin-api\";\nimport { readDurationFromConfig } from \"@backstage/config\";\nimport { createRouter } from \"./router\";\nimport { Hub, type HubOptions } from \"./hub\";\nimport { toEntityPath } from \"./entityPath\";\n\nexport const rwPlugin = createBackendPlugin({\n pluginId: \"rw\",\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n httpAuth: coreServices.httpAuth,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n },\n async init({ httpRouter, httpAuth, logger, config, scheduler }) {\n const projectDir = config.getOptionalString(\"rw.projectDir\");\n const entity = config.getOptionalString(\"rw.entity\");\n const cacheSize = config.getOptionalNumber(\"rw.cacheSize\");\n\n const s3Config = config.getOptionalConfig(\"rw.s3\");\n const s3 = s3Config\n ? {\n bucket: s3Config.getString(\"bucket\"),\n region: s3Config.getOptionalString(\"region\"),\n endpoint: s3Config.getOptionalString(\"endpoint\"),\n bucketRootPath: s3Config.getOptionalString(\"bucketRootPath\"),\n accessKeyId: s3Config.getOptionalString(\"accessKeyId\"),\n secretAccessKey: s3Config.getOptionalString(\"secretAccessKey\"),\n }\n : undefined;\n\n if (!projectDir && !s3) {\n throw new Error(\"Either rw.projectDir or rw.s3 must be configured\");\n }\n\n if (projectDir && !entity) {\n throw new Error(\"rw.entity is required when rw.projectDir is set\");\n }\n\n const diagramsConfig = config.getOptionalConfig(\"rw.diagrams\");\n const diagrams = diagramsConfig\n ? {\n krokiUrl: diagramsConfig.getOptionalString(\"krokiUrl\"),\n dpi: diagramsConfig.getOptionalNumber(\"dpi\"),\n }\n : undefined;\n\n const hubOptions: HubOptions = {\n projectDir,\n entity,\n s3,\n diagrams,\n cacheSize,\n };\n\n const hub = new Hub(hubOptions);\n\n if (s3) {\n logger.info(`Hub: S3 mode (bucket: ${s3.bucket}, cache size: ${cacheSize ?? 20})`);\n } else {\n logger.info(\n `Hub: local mode (${projectDir}, entity: ${entity ? toEntityPath(entity) : entity})`,\n );\n }\n\n if (config.has(\"rw.reloadInterval\")) {\n const frequency = readDurationFromConfig(config, { key: \"rw.reloadInterval\" });\n logger.info(`Scheduling site reload with interval: ${JSON.stringify(frequency)}`);\n\n await scheduler.scheduleTask({\n id: \"rw-site-reload\",\n frequency,\n timeout: frequency,\n scope: \"local\",\n fn: async () => hub.reloadAll(logger),\n });\n }\n\n const router = await createRouter({ logger, httpAuth, hub });\n httpRouter.use(router);\n httpRouter.addAuthPolicy({\n path: \"/health\",\n allow: \"unauthenticated\",\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","config","hub","Hub","toEntityPath","readDurationFromConfig","router","createRouter"],"mappings":"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"}
@@ -30,7 +30,7 @@ async function createRouter(options) {
30
30
  const site = res.locals.rwSite;
31
31
  const sectionRefParam = req.query.sectionRef;
32
32
  const sectionRef = typeof sectionRefParam === "string" ? sectionRefParam : null;
33
- const nav = site.getNavigation(sectionRef);
33
+ const nav = getNavigationOrThrow(site, sectionRef);
34
34
  res.json(nav);
35
35
  });
36
36
  router.get("/site/:namespace/:kind/:name/pages/", async (req, res) => {
@@ -39,7 +39,7 @@ async function createRouter(options) {
39
39
  const sectionRef = typeof sectionRefParam === "string" ? sectionRefParam : void 0;
40
40
  let pagePath = "";
41
41
  if (sectionRef) {
42
- const nav = site.getNavigation(sectionRef);
42
+ const nav = getNavigationOrThrow(site, sectionRef);
43
43
  if (nav.scope?.path) {
44
44
  pagePath = nav.scope.path.replace(/^\//, "");
45
45
  }
@@ -58,6 +58,13 @@ async function createRouter(options) {
58
58
  });
59
59
  return router;
60
60
  }
61
+ function getNavigationOrThrow(site, sectionRef) {
62
+ try {
63
+ return site.getNavigation(sectionRef);
64
+ } catch (err) {
65
+ throw toStorageError(err);
66
+ }
67
+ }
61
68
  async function renderPageOrThrow(site, pagePath) {
62
69
  try {
63
70
  return await site.renderPage(pagePath);
@@ -66,9 +73,16 @@ async function renderPageOrThrow(site, pagePath) {
66
73
  if (message.includes("Content not found")) {
67
74
  throw new errors.NotFoundError(`Page not found: /${pagePath}`);
68
75
  }
76
+ if (message.includes("Storage error")) {
77
+ throw toStorageError(err);
78
+ }
69
79
  throw err;
70
80
  }
71
81
  }
82
+ function toStorageError(err) {
83
+ const message = err instanceof Error ? err.message : String(err);
84
+ return new errors.ServiceUnavailableError(`Storage unavailable: ${message}`);
85
+ }
72
86
 
73
87
  exports.createRouter = createRouter;
74
88
  //# sourceMappingURL=router.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import Router from \"express-promise-router\";\nimport type { HttpAuthService, LoggerService } from \"@backstage/backend-plugin-api\";\nimport { InputError, NotFoundError } from \"@backstage/errors\";\nimport type { RwSite } from \"@rwdocs/core\";\nimport type { Hub } from \"./hub\";\n\nexport interface RouterOptions {\n logger: LoggerService;\n httpAuth: HttpAuthService;\n hub: Hub;\n}\n\nexport async function createRouter(options: RouterOptions) {\n const { hub } = options;\n const router = Router();\n\n router.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\" });\n });\n\n router.use(\"/site/:namespace/:kind/:name\", (req, res, next) => {\n const { namespace, kind, name } = req.params;\n const siteRef = `${namespace}/${kind}/${name}`.toLowerCase();\n\n const site = hub.getSite(siteRef);\n if (!site) {\n throw new NotFoundError(`No documentation site found for entity: ${siteRef}`);\n }\n\n res.locals.rwSite = site;\n next();\n });\n\n router.get(\"/site/:namespace/:kind/:name/config\", (_req, res) => {\n res.json({ liveReloadEnabled: false });\n });\n\n router.get(\"/site/:namespace/:kind/:name/navigation\", (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const sectionRefParam = req.query.sectionRef;\n const sectionRef = typeof sectionRefParam === \"string\" ? sectionRefParam : null;\n const nav = site.getNavigation(sectionRef);\n res.json(nav);\n });\n\n router.get(\"/site/:namespace/:kind/:name/pages/\", async (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const sectionRefParam = req.query.sectionRef;\n const sectionRef = typeof sectionRefParam === \"string\" ? sectionRefParam : undefined;\n\n let pagePath = \"\";\n if (sectionRef) {\n const nav = site.getNavigation(sectionRef);\n if (nav.scope?.path) {\n pagePath = nav.scope.path.replace(/^\\//, \"\");\n }\n }\n\n const page = await renderPageOrThrow(site, pagePath);\n res.json(page);\n });\n\n router.get(\"/site/:namespace/:kind/:name/pages/:path(*)\", async (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const pagePath = req.params.path || \"\";\n if (pagePath.split(\"/\").includes(\"..\")) {\n throw new InputError(\"Invalid path\");\n }\n const page = await renderPageOrThrow(site, pagePath);\n res.json(page);\n });\n\n return router;\n}\n\nasync function renderPageOrThrow(site: RwSite, pagePath: string) {\n try {\n return await site.renderPage(pagePath);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"Content not found\")) {\n throw new NotFoundError(`Page not found: /${pagePath}`);\n }\n throw err;\n }\n}\n"],"names":["Router","NotFoundError","InputError"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../src/router.ts"],"sourcesContent":["import Router from \"express-promise-router\";\nimport type { HttpAuthService, LoggerService } from \"@backstage/backend-plugin-api\";\nimport { InputError, NotFoundError, ServiceUnavailableError } from \"@backstage/errors\";\nimport type { RwSite } from \"@rwdocs/core\";\nimport type { Hub } from \"./hub\";\n\nexport interface RouterOptions {\n logger: LoggerService;\n httpAuth: HttpAuthService;\n hub: Hub;\n}\n\nexport async function createRouter(options: RouterOptions) {\n const { hub } = options;\n const router = Router();\n\n router.get(\"/health\", (_req, res) => {\n res.json({ status: \"ok\" });\n });\n\n router.use(\"/site/:namespace/:kind/:name\", (req, res, next) => {\n const { namespace, kind, name } = req.params;\n const siteRef = `${namespace}/${kind}/${name}`.toLowerCase();\n\n const site = hub.getSite(siteRef);\n if (!site) {\n throw new NotFoundError(`No documentation site found for entity: ${siteRef}`);\n }\n\n res.locals.rwSite = site;\n next();\n });\n\n router.get(\"/site/:namespace/:kind/:name/config\", (_req, res) => {\n res.json({ liveReloadEnabled: false });\n });\n\n router.get(\"/site/:namespace/:kind/:name/navigation\", (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const sectionRefParam = req.query.sectionRef;\n const sectionRef = typeof sectionRefParam === \"string\" ? sectionRefParam : null;\n const nav = getNavigationOrThrow(site, sectionRef);\n res.json(nav);\n });\n\n router.get(\"/site/:namespace/:kind/:name/pages/\", async (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const sectionRefParam = req.query.sectionRef;\n const sectionRef = typeof sectionRefParam === \"string\" ? sectionRefParam : undefined;\n\n let pagePath = \"\";\n if (sectionRef) {\n const nav = getNavigationOrThrow(site, sectionRef);\n if (nav.scope?.path) {\n pagePath = nav.scope.path.replace(/^\\//, \"\");\n }\n }\n\n const page = await renderPageOrThrow(site, pagePath);\n res.json(page);\n });\n\n router.get(\"/site/:namespace/:kind/:name/pages/:path(*)\", async (req, res) => {\n const site: RwSite = res.locals.rwSite;\n const pagePath = req.params.path || \"\";\n if (pagePath.split(\"/\").includes(\"..\")) {\n throw new InputError(\"Invalid path\");\n }\n const page = await renderPageOrThrow(site, pagePath);\n res.json(page);\n });\n\n return router;\n}\n\nfunction getNavigationOrThrow(site: RwSite, sectionRef: string | null) {\n try {\n return site.getNavigation(sectionRef);\n } catch (err) {\n throw toStorageError(err);\n }\n}\n\nasync function renderPageOrThrow(site: RwSite, pagePath: string) {\n try {\n return await site.renderPage(pagePath);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (message.includes(\"Content not found\")) {\n throw new NotFoundError(`Page not found: /${pagePath}`);\n }\n // Message prefix comes from @rwdocs/core native addon (RenderError::Storage).\n // Must be updated if the upstream error format changes.\n if (message.includes(\"Storage error\")) {\n throw toStorageError(err);\n }\n throw err;\n }\n}\n\nfunction toStorageError(err: unknown): ServiceUnavailableError {\n const message = err instanceof Error ? err.message : String(err);\n return new ServiceUnavailableError(`Storage unavailable: ${message}`);\n}\n"],"names":["Router","NotFoundError","InputError","ServiceUnavailableError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rwdocs/backstage-plugin-rw-backend",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT OR Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -54,8 +54,10 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@backstage/catalog-model": "^1.7.6",
57
+ "@backstage/config": "^1.3.6",
57
58
  "@backstage/errors": "^1.2.7",
58
- "@rwdocs/core": "^0.1.18",
59
+ "@backstage/types": "^1.2.2",
60
+ "@rwdocs/core": "^0.1.21",
59
61
  "express": "^4.21.0",
60
62
  "express-promise-router": "^4.1.0"
61
63
  },