@rwdocs/backstage-plugin-rw-backend 0.1.1 → 0.1.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/config.d.ts +16 -7
- package/dist/entityPath.cjs.js +12 -0
- package/dist/entityPath.cjs.js.map +1 -0
- package/dist/hub.cjs.js +59 -0
- package/dist/hub.cjs.js.map +1 -0
- package/dist/plugin.cjs.js +33 -28
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/router.cjs.js +45 -15
- package/dist/router.cjs.js.map +1 -1
- package/package.json +5 -4
package/config.d.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
export interface Config {
|
|
2
2
|
/** @visibility backend */
|
|
3
3
|
rw?: {
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Local directory containing documentation source files.
|
|
6
|
+
* Mutually exclusive with `s3`.
|
|
7
|
+
*/
|
|
5
8
|
projectDir?: string;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Entity ref that the local projectDir serves as (required when projectDir is set).
|
|
11
|
+
* Standard Backstage entity ref format: "kind:namespace/name" (e.g. "component:default/my-docs")
|
|
12
|
+
*/
|
|
13
|
+
entity?: string;
|
|
14
|
+
/** Maximum number of cached RwSite instances. Default: 20. */
|
|
15
|
+
cacheSize?: number;
|
|
16
|
+
/**
|
|
17
|
+
* S3 storage configuration. Shared across all entity sites.
|
|
18
|
+
* Mutually exclusive with `projectDir`.
|
|
19
|
+
*/
|
|
9
20
|
s3?: {
|
|
10
21
|
/** S3 bucket name. */
|
|
11
22
|
bucket: string;
|
|
12
|
-
/** Entity identifier (prefix) within the bucket. */
|
|
13
|
-
entity: string;
|
|
14
23
|
/** AWS region. */
|
|
15
24
|
region?: string;
|
|
16
25
|
/** Custom S3 endpoint URL. */
|
|
@@ -25,7 +34,7 @@ export interface Config {
|
|
|
25
34
|
*/
|
|
26
35
|
secretAccessKey?: string;
|
|
27
36
|
};
|
|
28
|
-
/** Diagram rendering configuration. */
|
|
37
|
+
/** Diagram rendering configuration. Shared across all sites. */
|
|
29
38
|
diagrams?: {
|
|
30
39
|
/** Kroki server URL for rendering diagrams. */
|
|
31
40
|
krokiUrl?: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
4
|
+
require('@backstage/errors');
|
|
5
|
+
|
|
6
|
+
function toEntityPath(entityRef) {
|
|
7
|
+
const ref = catalogModel.parseEntityRef(entityRef);
|
|
8
|
+
return `${ref.namespace}/${ref.kind}/${ref.name}`.toLocaleLowerCase("en-US");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.toEntityPath = toEntityPath;
|
|
12
|
+
//# sourceMappingURL=entityPath.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityPath.cjs.js","sources":["../src/entityPath.ts"],"sourcesContent":["import { parseEntityRef } from \"@backstage/catalog-model\";\nimport { InputError } from \"@backstage/errors\";\n\n/**\n * Converts a Backstage entity ref (e.g. \"component:default/arch\") to the\n * slash-delimited, lowercased path format used in URLs and cache keys\n * (e.g. \"default/component/arch\").\n *\n * Uses namespace/kind/name ordering to match Backstage catalog URL convention.\n *\n * NOTE: The frontend plugin has a similar utility at\n * plugins/rw/src/components/entityPath.ts — keep in sync if changing logic.\n */\nexport function toEntityPath(entityRef: string): string {\n const ref = parseEntityRef(entityRef);\n return `${ref.namespace}/${ref.kind}/${ref.name}`.toLocaleLowerCase(\"en-US\");\n}\n\n/**\n * Converts a slash-delimited entity path (e.g. \"default/component/arch\")\n * back to the standard Backstage entity ref format (e.g. \"component:default/arch\").\n *\n * This is the inverse of `toEntityPath`. Note that the round-trip always\n * produces lowercased refs since `toEntityPath` lowercases its output.\n */\nexport function fromEntityPath(path: string): string {\n const parts = path.split(\"/\");\n if (parts.length !== 3 || parts.some((p) => !p)) {\n throw new InputError(`Invalid entity path: \"${path}\" (expected \"namespace/kind/name\")`);\n }\n const [namespace, kind, name] = parts;\n return `${kind}:${namespace}/${name}`;\n}\n"],"names":["parseEntityRef"],"mappings":";;;;;AAaO,SAAS,aAAa,SAAA,EAA2B;AACtD,EAAA,MAAM,GAAA,GAAMA,4BAAe,SAAS,CAAA;AACpC,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,SAAS,CAAA,CAAA,EAAI,GAAA,CAAI,IAAI,CAAA,CAAA,EAAI,GAAA,CAAI,IAAI,CAAA,CAAA,CAAG,iBAAA,CAAkB,OAAO,CAAA;AAC7E;;;;"}
|
package/dist/hub.cjs.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@rwdocs/core');
|
|
4
|
+
var entityPath = require('./entityPath.cjs.js');
|
|
5
|
+
|
|
6
|
+
class Hub {
|
|
7
|
+
options;
|
|
8
|
+
cache = /* @__PURE__ */ new Map();
|
|
9
|
+
maxSize;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = {
|
|
12
|
+
...options,
|
|
13
|
+
entity: options.entity ? entityPath.toEntityPath(options.entity) : void 0
|
|
14
|
+
};
|
|
15
|
+
this.maxSize = options.cacheSize ?? 20;
|
|
16
|
+
}
|
|
17
|
+
getSite(entityRef) {
|
|
18
|
+
if (this.options.projectDir) {
|
|
19
|
+
return this.getLocalSite(entityRef);
|
|
20
|
+
}
|
|
21
|
+
return this.getS3Site(entityRef);
|
|
22
|
+
}
|
|
23
|
+
getLocalSite(entityRef) {
|
|
24
|
+
if (entityRef !== this.options.entity) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const cached = this.cache.get(entityRef);
|
|
28
|
+
if (cached) return cached;
|
|
29
|
+
const site = core.createSite({
|
|
30
|
+
projectDir: this.options.projectDir,
|
|
31
|
+
diagrams: this.options.diagrams
|
|
32
|
+
});
|
|
33
|
+
this.cache.set(entityRef, site);
|
|
34
|
+
return site;
|
|
35
|
+
}
|
|
36
|
+
getS3Site(entityRef) {
|
|
37
|
+
const cached = this.cache.get(entityRef);
|
|
38
|
+
if (cached) {
|
|
39
|
+
this.cache.delete(entityRef);
|
|
40
|
+
this.cache.set(entityRef, cached);
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
if (this.cache.size >= this.maxSize) {
|
|
44
|
+
const firstKey = this.cache.keys().next().value;
|
|
45
|
+
this.cache.delete(firstKey);
|
|
46
|
+
}
|
|
47
|
+
const s3 = this.options.s3;
|
|
48
|
+
const config = {
|
|
49
|
+
s3: { ...s3, entity: entityRef },
|
|
50
|
+
diagrams: this.options.diagrams
|
|
51
|
+
};
|
|
52
|
+
const site = core.createSite(config);
|
|
53
|
+
this.cache.set(entityRef, site);
|
|
54
|
+
return site;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.Hub = Hub;
|
|
59
|
+
//# sourceMappingURL=hub.cjs.js.map
|
|
@@ -0,0 +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;;;;"}
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
4
|
var router = require('./router.cjs.js');
|
|
5
|
+
var hub = require('./hub.cjs.js');
|
|
6
|
+
var entityPath = require('./entityPath.cjs.js');
|
|
5
7
|
|
|
6
8
|
const rwPlugin = backendPluginApi.createBackendPlugin({
|
|
7
9
|
pluginId: "rw",
|
|
@@ -15,41 +17,44 @@ const rwPlugin = backendPluginApi.createBackendPlugin({
|
|
|
15
17
|
},
|
|
16
18
|
async init({ httpRouter, httpAuth, logger, config }) {
|
|
17
19
|
const projectDir = config.getOptionalString("rw.projectDir");
|
|
18
|
-
|
|
20
|
+
const entity = config.getOptionalString("rw.entity");
|
|
21
|
+
const cacheSize = config.getOptionalNumber("rw.cacheSize");
|
|
19
22
|
const s3Config = config.getOptionalConfig("rw.s3");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
secretAccessKey: s3Config.getOptionalString("secretAccessKey")
|
|
29
|
-
};
|
|
30
|
-
}
|
|
23
|
+
const s3 = s3Config ? {
|
|
24
|
+
bucket: s3Config.getString("bucket"),
|
|
25
|
+
region: s3Config.getOptionalString("region"),
|
|
26
|
+
endpoint: s3Config.getOptionalString("endpoint"),
|
|
27
|
+
bucketRootPath: s3Config.getOptionalString("bucketRootPath"),
|
|
28
|
+
accessKeyId: s3Config.getOptionalString("accessKeyId"),
|
|
29
|
+
secretAccessKey: s3Config.getOptionalString("secretAccessKey")
|
|
30
|
+
} : void 0;
|
|
31
31
|
if (!projectDir && !s3) {
|
|
32
32
|
throw new Error("Either rw.projectDir or rw.s3 must be configured");
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (diagramsConfig) {
|
|
37
|
-
diagrams = {
|
|
38
|
-
krokiUrl: diagramsConfig.getOptionalString("krokiUrl"),
|
|
39
|
-
dpi: diagramsConfig.getOptionalNumber("dpi")
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const linkPrefix = config.getOptionalString("rw.linkPrefix");
|
|
43
|
-
if (linkPrefix) {
|
|
44
|
-
logger.info(`Using link prefix: ${linkPrefix}`);
|
|
34
|
+
if (projectDir && !entity) {
|
|
35
|
+
throw new Error("rw.entity is required when rw.projectDir is set");
|
|
45
36
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
37
|
+
const diagramsConfig = config.getOptionalConfig("rw.diagrams");
|
|
38
|
+
const diagrams = diagramsConfig ? {
|
|
39
|
+
krokiUrl: diagramsConfig.getOptionalString("krokiUrl"),
|
|
40
|
+
dpi: diagramsConfig.getOptionalNumber("dpi")
|
|
41
|
+
} : void 0;
|
|
42
|
+
const hubOptions = {
|
|
48
43
|
projectDir,
|
|
44
|
+
entity,
|
|
49
45
|
s3,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
46
|
+
diagrams,
|
|
47
|
+
cacheSize
|
|
48
|
+
};
|
|
49
|
+
const hub$1 = new hub.Hub(hubOptions);
|
|
50
|
+
if (s3) {
|
|
51
|
+
logger.info(`Hub: S3 mode (bucket: ${s3.bucket}, cache size: ${cacheSize ?? 20})`);
|
|
52
|
+
} else {
|
|
53
|
+
logger.info(
|
|
54
|
+
`Hub: local mode (${projectDir}, entity: ${entity ? entityPath.toEntityPath(entity) : entity})`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const router$1 = await router.createRouter({ hub: hub$1 });
|
|
53
58
|
httpRouter.use(router$1);
|
|
54
59
|
httpRouter.addAuthPolicy({
|
|
55
60
|
path: "/health",
|
package/dist/plugin.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import { coreServices, createBackendPlugin } from \"@backstage/backend-plugin-api\";\nimport { createRouter
|
|
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":";;;;;;;AAKO,MAAM,WAAWA,oCAAA,CAAoB;AAAA,EAC1C,QAAA,EAAU,IAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,YAAYC,6BAAA,CAAa,UAAA;AAAA,QACzB,UAAUA,6BAAA,CAAa,QAAA;AAAA,QACvB,QAAQA,6BAAA,CAAa,MAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,YAAY,QAAA,EAAU,MAAA,EAAQ,QAAO,EAAG;AACnD,QAAA,MAAM,UAAA,GAAa,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AAC3D,QAAA,MAAM,MAAA,GAAS,MAAA,CAAO,iBAAA,CAAkB,WAAW,CAAA;AACnD,QAAA,MAAM,SAAA,GAAY,MAAA,CAAO,iBAAA,CAAkB,cAAc,CAAA;AAEzD,QAAA,MAAM,QAAA,GAAW,MAAA,CAAO,iBAAA,CAAkB,OAAO,CAAA;AACjD,QAAA,MAAM,KAAK,QAAA,GACP;AAAA,UACE,MAAA,EAAQ,QAAA,CAAS,SAAA,CAAU,QAAQ,CAAA;AAAA,UACnC,MAAA,EAAQ,QAAA,CAAS,iBAAA,CAAkB,QAAQ,CAAA;AAAA,UAC3C,QAAA,EAAU,QAAA,CAAS,iBAAA,CAAkB,UAAU,CAAA;AAAA,UAC/C,cAAA,EAAgB,QAAA,CAAS,iBAAA,CAAkB,gBAAgB,CAAA;AAAA,UAC3D,WAAA,EAAa,QAAA,CAAS,iBAAA,CAAkB,aAAa,CAAA;AAAA,UACrD,eAAA,EAAiB,QAAA,CAAS,iBAAA,CAAkB,iBAAiB;AAAA,SAC/D,GACA,MAAA;AAEJ,QAAA,IAAI,CAAC,UAAA,IAAc,CAAC,EAAA,EAAI;AACtB,UAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,QACpE;AAEA,QAAA,IAAI,UAAA,IAAc,CAAC,MAAA,EAAQ;AACzB,UAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,QACnE;AAEA,QAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,iBAAA,CAAkB,aAAa,CAAA;AAC7D,QAAA,MAAM,WAAW,cAAA,GACb;AAAA,UACE,QAAA,EAAU,cAAA,CAAe,iBAAA,CAAkB,UAAU,CAAA;AAAA,UACrD,GAAA,EAAK,cAAA,CAAe,iBAAA,CAAkB,KAAK;AAAA,SAC7C,GACA,MAAA;AAEJ,QAAA,MAAM,UAAA,GAAyB;AAAA,UAC7B,UAAA;AAAA,UACA,MAAA;AAAA,UACA,EAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACF;AAEA,QAAA,MAAMC,KAAA,GAAM,IAAIC,OAAA,CAAI,UAAU,CAAA;AAE9B,QAAA,IAAI,EAAA,EAAI;AACN,UAAA,MAAA,CAAO,KAAK,CAAA,sBAAA,EAAyB,EAAA,CAAG,MAAM,CAAA,cAAA,EAAiB,SAAA,IAAa,EAAE,CAAA,CAAA,CAAG,CAAA;AAAA,QACnF,CAAA,MAAO;AACL,UAAA,MAAA,CAAO,IAAA;AAAA,YACL,oBAAoB,UAAU,CAAA,UAAA,EAAa,SAASC,uBAAA,CAAa,MAAM,IAAI,MAAM,CAAA,CAAA;AAAA,WACnF;AAAA,QACF;AAEA,QAAA,MAAMC,WAAS,MAAMC,mBAAA,CAAa,OAAoBJ,OAAK,CAAA;AAC3D,QAAA,UAAA,CAAW,IAAIG,QAAM,CAAA;AACrB,QAAA,UAAA,CAAW,aAAA,CAAc;AAAA,UACvB,IAAA,EAAM,SAAA;AAAA,UACN,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
package/dist/router.cjs.js
CHANGED
|
@@ -2,37 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
var Router = require('express-promise-router');
|
|
4
4
|
var errors = require('@backstage/errors');
|
|
5
|
-
var core = require('@rwdocs/core');
|
|
6
5
|
|
|
7
6
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
8
7
|
|
|
9
8
|
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
10
9
|
|
|
11
10
|
async function createRouter(options) {
|
|
12
|
-
const {
|
|
11
|
+
const { hub } = options;
|
|
13
12
|
const router = Router__default.default();
|
|
14
|
-
const config = { projectDir, s3, linkPrefix, diagrams };
|
|
15
|
-
logger.info(
|
|
16
|
-
s3 ? `Creating RW site from S3 (${s3.bucket}/${s3.entity})` : `Creating RW site from ${projectDir}`
|
|
17
|
-
);
|
|
18
|
-
const site = core.createSite(config);
|
|
19
13
|
router.get("/health", (_req, res) => {
|
|
20
14
|
res.json({ status: "ok" });
|
|
21
15
|
});
|
|
22
|
-
router.
|
|
16
|
+
router.use("/site/:namespace/:kind/:name", (req, res, next) => {
|
|
17
|
+
const { namespace, kind, name } = req.params;
|
|
18
|
+
const siteRef = `${namespace}/${kind}/${name}`.toLowerCase();
|
|
19
|
+
const site = hub.getSite(siteRef);
|
|
20
|
+
if (!site) {
|
|
21
|
+
throw new errors.NotFoundError(`No documentation site found for entity: ${siteRef}`);
|
|
22
|
+
}
|
|
23
|
+
res.locals.rwSite = site;
|
|
24
|
+
next();
|
|
25
|
+
});
|
|
26
|
+
router.get("/site/:namespace/:kind/:name/config", (_req, res) => {
|
|
23
27
|
res.json({ liveReloadEnabled: false });
|
|
24
28
|
});
|
|
25
|
-
router.get("/navigation", (req, res) => {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
+
router.get("/site/:namespace/:kind/:name/navigation", (req, res) => {
|
|
30
|
+
const site = res.locals.rwSite;
|
|
31
|
+
const sectionRefParam = req.query.sectionRef;
|
|
32
|
+
const sectionRef = typeof sectionRefParam === "string" ? sectionRefParam : null;
|
|
33
|
+
const nav = getNavigationOrThrow(site, sectionRef);
|
|
29
34
|
res.json(nav);
|
|
30
35
|
});
|
|
31
|
-
router.get("/pages/", async (
|
|
32
|
-
const
|
|
36
|
+
router.get("/site/:namespace/:kind/:name/pages/", async (req, res) => {
|
|
37
|
+
const site = res.locals.rwSite;
|
|
38
|
+
const sectionRefParam = req.query.sectionRef;
|
|
39
|
+
const sectionRef = typeof sectionRefParam === "string" ? sectionRefParam : void 0;
|
|
40
|
+
let pagePath = "";
|
|
41
|
+
if (sectionRef) {
|
|
42
|
+
const nav = getNavigationOrThrow(site, sectionRef);
|
|
43
|
+
if (nav.scope?.path) {
|
|
44
|
+
pagePath = nav.scope.path.replace(/^\//, "");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const page = await renderPageOrThrow(site, pagePath);
|
|
33
48
|
res.json(page);
|
|
34
49
|
});
|
|
35
|
-
router.get("/pages/:path(*)", async (req, res) => {
|
|
50
|
+
router.get("/site/:namespace/:kind/:name/pages/:path(*)", async (req, res) => {
|
|
51
|
+
const site = res.locals.rwSite;
|
|
36
52
|
const pagePath = req.params.path || "";
|
|
37
53
|
if (pagePath.split("/").includes("..")) {
|
|
38
54
|
throw new errors.InputError("Invalid path");
|
|
@@ -42,6 +58,13 @@ async function createRouter(options) {
|
|
|
42
58
|
});
|
|
43
59
|
return router;
|
|
44
60
|
}
|
|
61
|
+
function getNavigationOrThrow(site, sectionRef) {
|
|
62
|
+
try {
|
|
63
|
+
return site.getNavigation(sectionRef);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
throw toStorageError(err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
45
68
|
async function renderPageOrThrow(site, pagePath) {
|
|
46
69
|
try {
|
|
47
70
|
return await site.renderPage(pagePath);
|
|
@@ -50,9 +73,16 @@ async function renderPageOrThrow(site, pagePath) {
|
|
|
50
73
|
if (message.includes("Content not found")) {
|
|
51
74
|
throw new errors.NotFoundError(`Page not found: /${pagePath}`);
|
|
52
75
|
}
|
|
76
|
+
if (message.includes("Storage error")) {
|
|
77
|
+
throw toStorageError(err);
|
|
78
|
+
}
|
|
53
79
|
throw err;
|
|
54
80
|
}
|
|
55
81
|
}
|
|
82
|
+
function toStorageError(err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
return new errors.ServiceUnavailableError(`Storage unavailable: ${message}`);
|
|
85
|
+
}
|
|
56
86
|
|
|
57
87
|
exports.createRouter = createRouter;
|
|
58
88
|
//# sourceMappingURL=router.cjs.js.map
|
package/dist/router.cjs.js.map
CHANGED
|
@@ -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 {
|
|
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.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT OR Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
"postpack": "backstage-cli package postpack"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"@backstage/catalog-model": "^1.7.6",
|
|
56
57
|
"@backstage/errors": "^1.2.7",
|
|
57
|
-
"@rwdocs/core": "^0.1.
|
|
58
|
+
"@rwdocs/core": "^0.1.19",
|
|
58
59
|
"express": "^4.21.0",
|
|
59
60
|
"express-promise-router": "^4.1.0"
|
|
60
61
|
},
|
|
@@ -64,14 +65,14 @@
|
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@backstage/backend-plugin-api": "^1.0.0",
|
|
66
67
|
"@backstage/backend-test-utils": "^1.11.0",
|
|
67
|
-
"@backstage/cli": "^0.
|
|
68
|
+
"@backstage/cli": "^0.36.0",
|
|
68
69
|
"@jest/environment-jsdom-abstract": "^30.2.0",
|
|
69
70
|
"@types/express": "^4.17.0",
|
|
70
71
|
"@types/jest": "^30.0.0",
|
|
71
72
|
"@types/jsdom": "^28",
|
|
72
73
|
"@types/supertest": "^7.2.0",
|
|
73
74
|
"jest": "^30.2.0",
|
|
74
|
-
"jsdom": "^
|
|
75
|
+
"jsdom": "^29.0.0",
|
|
75
76
|
"prettier": "^3.4.2",
|
|
76
77
|
"supertest": "^7.2.2",
|
|
77
78
|
"typescript": "^5.7.0"
|