@cap-js/ord 1.2.0 → 1.3.1
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/LICENSE +1 -1
- package/README.md +116 -8
- package/cds-plugin.js +17 -8
- package/data/well-known.json +2 -2
- package/lib/authentication.js +153 -0
- package/lib/build.js +85 -0
- package/lib/constants.js +80 -4
- package/lib/date.js +11 -16
- package/lib/defaults.js +68 -44
- package/lib/es-module.mjs +6 -0
- package/lib/extendOrdWithCustom.js +12 -6
- package/lib/index.js +5 -3
- package/lib/logger.js +9 -3
- package/lib/metaData.js +28 -14
- package/lib/ord-service.cds +3 -0
- package/lib/ord-service.mjs +32 -0
- package/lib/ord.js +233 -93
- package/lib/templates.js +278 -71
- package/package.json +21 -8
- package/lib/plugin.js +0 -33
package/lib/defaults.js
CHANGED
|
@@ -1,68 +1,97 @@
|
|
|
1
|
-
const { OPEN_RESOURCE_DISCOVERY_VERSION } = require("./constants");
|
|
1
|
+
const { OPEN_RESOURCE_DISCOVERY_VERSION, SHORT_DESCRIPTION_PREFIX, RESOURCE_VISIBILITY } = require("./constants");
|
|
2
|
+
const { getAuthConfig } = require("./authentication");
|
|
3
|
+
const _ = require("lodash");
|
|
4
|
+
|
|
5
|
+
const packageTypes = [
|
|
6
|
+
{ tag: "-api", type: "APIs" },
|
|
7
|
+
{ tag: "-event", type: "Events" },
|
|
8
|
+
{ tag: "-consumptionBundle", type: "Consumption Bundles" },
|
|
9
|
+
{ tag: "-integrationDependency", type: "Integration Dependencies" },
|
|
10
|
+
{ tag: "-entityType", type: "Entity Types" },
|
|
11
|
+
{ tag: "-dataProduct", type: "Data Products" },
|
|
12
|
+
];
|
|
2
13
|
|
|
3
14
|
const regexWithRemoval = (name) => {
|
|
4
|
-
|
|
5
|
-
return name.replace(/[^a-zA-Z0-9]/g, "");
|
|
6
|
-
}
|
|
15
|
+
return name?.replace(/[^a-zA-Z0-9]/g, "");
|
|
7
16
|
};
|
|
8
17
|
|
|
9
18
|
const nameWithDot = (name) => {
|
|
10
|
-
return (
|
|
11
|
-
regexWithRemoval(name.charAt(0)) +
|
|
12
|
-
name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, ".")
|
|
13
|
-
);
|
|
19
|
+
return regexWithRemoval(name.charAt(0)) + name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, ".");
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
const nameWithSpaces = (name) => {
|
|
17
|
-
return (
|
|
18
|
-
regexWithRemoval(name.charAt(0)) +
|
|
19
|
-
name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, " ")
|
|
20
|
-
);
|
|
23
|
+
return regexWithRemoval(name.charAt(0)) + name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, " ");
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
const defaultProductOrdId = (name) => `customer:product:${nameWithDot(name)}:`;
|
|
24
27
|
|
|
28
|
+
const generatePackageDescriptions = (name, type, visibility) => ({
|
|
29
|
+
shortDescription: `Package containing ${visibility} ${type}`,
|
|
30
|
+
description: `This package contains ${visibility} ${type} for ${nameWithSpaces(name)}.`,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const generateUniquePackageOrdId = (ordNamespace, name, tag, visibility) => {
|
|
34
|
+
const visibilitySuffix = visibility === RESOURCE_VISIBILITY.public ? "" : `-${visibility}`;
|
|
35
|
+
return `${ordNamespace}:package:${regexWithRemoval(name)}${tag}${visibilitySuffix}:v1`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks if at least one policy level in the array is SAP.
|
|
40
|
+
*
|
|
41
|
+
* @param {string[]} policyLevels - Array of policy levels.
|
|
42
|
+
*/
|
|
43
|
+
function hasSAPPolicyLevel(policyLevels) {
|
|
44
|
+
return policyLevels.some((policyLevel) => policyLevel.split(":")[0].toLowerCase() === "sap");
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
/**
|
|
26
48
|
* Module containing default configuration for ORD Document.
|
|
27
49
|
* @module defaults
|
|
28
50
|
*/
|
|
29
51
|
module.exports = {
|
|
30
|
-
$schema:
|
|
31
|
-
"https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json",
|
|
52
|
+
$schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
|
|
32
53
|
openResourceDiscovery: OPEN_RESOURCE_DISCOVERY_VERSION,
|
|
33
|
-
|
|
54
|
+
policyLevels: ["none"],
|
|
34
55
|
description: "this is an application description",
|
|
35
56
|
products: (name) => [
|
|
36
57
|
{
|
|
37
58
|
ordId: defaultProductOrdId(name),
|
|
38
59
|
title: nameWithSpaces(name),
|
|
39
|
-
shortDescription:
|
|
60
|
+
shortDescription: SHORT_DESCRIPTION_PREFIX + nameWithSpaces(name),
|
|
40
61
|
vendor: "customer:vendor:customer:",
|
|
41
62
|
},
|
|
42
63
|
],
|
|
43
64
|
groupTypeId: "sap.cds:service",
|
|
44
|
-
packages: function getPackageData(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
packages: function getPackageData(appConfig, policyLevels) {
|
|
66
|
+
const name = appConfig.appName;
|
|
67
|
+
const ordNamespace = appConfig.ordNamespace;
|
|
68
|
+
const productsOrdId = appConfig.existingProductORDId || appConfig.products?.[0]?.ordId;
|
|
69
|
+
const vendor = appConfig.env?.packages?.[0]?.vendor;
|
|
70
|
+
if (hasSAPPolicyLevel(policyLevels)) {
|
|
71
|
+
return _.uniqBy(
|
|
72
|
+
packageTypes.flatMap(({ tag, type }) =>
|
|
73
|
+
Object.values(RESOURCE_VISIBILITY).map((visibility) => ({
|
|
74
|
+
ordId: generateUniquePackageOrdId(ordNamespace, name, tag, visibility),
|
|
75
|
+
title: nameWithSpaces(name),
|
|
76
|
+
...generatePackageDescriptions(name, type, visibility),
|
|
77
|
+
version: "1.0.0",
|
|
78
|
+
...(productsOrdId && { partOfProducts: [productsOrdId || defaultProductOrdId(name)] }),
|
|
79
|
+
vendor: vendor || "customer:vendor:Customer:",
|
|
80
|
+
})),
|
|
81
|
+
),
|
|
82
|
+
"ordId",
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
60
85
|
return [
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
{
|
|
87
|
+
ordId: generateUniquePackageOrdId(ordNamespace, name, "", RESOURCE_VISIBILITY.public),
|
|
88
|
+
title: nameWithSpaces(name),
|
|
89
|
+
...generatePackageDescriptions(name, "General", RESOURCE_VISIBILITY.public),
|
|
90
|
+
version: "1.0.0",
|
|
91
|
+
...(productsOrdId && { partOfProducts: [productsOrdId || defaultProductOrdId(name)] }),
|
|
92
|
+
vendor: vendor || "customer:vendor:Customer:",
|
|
93
|
+
},
|
|
63
94
|
];
|
|
64
|
-
} else {
|
|
65
|
-
return [createPackage(name, ":v1")];
|
|
66
95
|
}
|
|
67
96
|
},
|
|
68
97
|
consumptionBundles: (appConfig) => [
|
|
@@ -71,8 +100,7 @@ module.exports = {
|
|
|
71
100
|
version: "1.0.0",
|
|
72
101
|
lastUpdate: appConfig.lastUpdate,
|
|
73
102
|
title: "Unprotected resources",
|
|
74
|
-
shortDescription:
|
|
75
|
-
"If we have another protected API then it will be another object",
|
|
103
|
+
shortDescription: "If we have another protected API then it will be another object",
|
|
76
104
|
description:
|
|
77
105
|
"This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication",
|
|
78
106
|
},
|
|
@@ -85,12 +113,8 @@ module.exports = {
|
|
|
85
113
|
openResourceDiscoveryV1: {
|
|
86
114
|
documents: [
|
|
87
115
|
{
|
|
88
|
-
url: "/
|
|
89
|
-
accessStrategies:
|
|
90
|
-
{
|
|
91
|
-
type: "open",
|
|
92
|
-
},
|
|
93
|
-
],
|
|
116
|
+
url: "/ord/v1/documents/ord-document",
|
|
117
|
+
accessStrategies: getAuthConfig().accessStrategies,
|
|
94
118
|
},
|
|
95
119
|
],
|
|
96
120
|
},
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// matches commonjs.js to es-module so that we can use the same code in cap services
|
|
2
|
+
export { default as defaults } from "./defaults.js";
|
|
3
|
+
export { default as getMetadata } from "./metaData.js";
|
|
4
|
+
export { default as ord } from "./ord.js";
|
|
5
|
+
export { default as authentication } from "./authentication.js";
|
|
6
|
+
export { default as logger } from "./logger.js";
|
|
@@ -9,7 +9,7 @@ function cleanNullProperties(obj) {
|
|
|
9
9
|
for (const key in obj) {
|
|
10
10
|
if (obj[key] === null) {
|
|
11
11
|
delete obj[key];
|
|
12
|
-
} else if (typeof obj[key] ===
|
|
12
|
+
} else if (typeof obj[key] === "object") {
|
|
13
13
|
cleanNullProperties(obj[key]);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -33,8 +33,12 @@ function compareAndHandleCustomORDContentWithExistingContent(ordContent, customO
|
|
|
33
33
|
const clonedOrdContent = structuredClone(ordContent);
|
|
34
34
|
for (const key in customORDContent) {
|
|
35
35
|
const propertyType = typeof customORDContent[key];
|
|
36
|
-
if (propertyType !==
|
|
37
|
-
Logger.warn(
|
|
36
|
+
if (propertyType !== "object" && propertyType !== "array") {
|
|
37
|
+
Logger.warn(
|
|
38
|
+
"Found ord top level primitive ord property in customOrdFile:",
|
|
39
|
+
key,
|
|
40
|
+
". Please define it in .cdsrc.json.",
|
|
41
|
+
);
|
|
38
42
|
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
@@ -50,15 +54,17 @@ function compareAndHandleCustomORDContentWithExistingContent(ordContent, customO
|
|
|
50
54
|
function getCustomORDContent(appConfig) {
|
|
51
55
|
if (!appConfig.env?.customOrdContentFile) return;
|
|
52
56
|
const pathToCustomORDContent = path.join(cds.root, appConfig.env?.customOrdContentFile);
|
|
53
|
-
if (fs.existsSync(pathToCustomORDContent))
|
|
54
|
-
Logger.error(
|
|
57
|
+
if (fs.existsSync(pathToCustomORDContent)) {
|
|
58
|
+
Logger.error("Custom ORD content file not found at", pathToCustomORDContent);
|
|
55
59
|
return require(pathToCustomORDContent);
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function extendCustomORDContentIfExists(appConfig, ordContent) {
|
|
60
64
|
const customORDContent = getCustomORDContent(appConfig);
|
|
61
|
-
return customORDContent
|
|
65
|
+
return customORDContent
|
|
66
|
+
? compareAndHandleCustomORDContentWithExistingContent(ordContent, customORDContent)
|
|
67
|
+
: ordContent;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
module.exports = {
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
defaults: require("./defaults.js"),
|
|
3
|
+
getMetadata: require("./metaData.js"),
|
|
4
|
+
ord: require("./ord.js"),
|
|
5
|
+
Logger: require("./logger.js"),
|
|
6
|
+
constants: require("./constants.js"),
|
|
5
7
|
};
|
package/lib/logger.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
const cds = require("@sap/cds");
|
|
2
2
|
|
|
3
|
+
const _debugLevel = cds.env?.DEBUG || process.env.DEBUG;
|
|
4
|
+
|
|
5
|
+
function _getLogLevel(debugLevel, logLevels) {
|
|
6
|
+
return debugLevel ? logLevels.DEBUG : logLevels.WARN;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const level = _getLogLevel(_debugLevel, cds.log?.levels);
|
|
10
|
+
|
|
3
11
|
const Logger = cds.log("ord-plugin", {
|
|
4
|
-
level
|
|
5
|
-
? cds.log.levels?.DEBUG
|
|
6
|
-
: cds.log.levels?.WARN,
|
|
12
|
+
level,
|
|
7
13
|
});
|
|
8
14
|
|
|
9
15
|
module.exports = {
|
package/lib/metaData.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
const cds = require(
|
|
2
|
-
const { compile: openapi } = require(
|
|
3
|
-
const { compile: asyncapi } = require(
|
|
4
|
-
const { COMPILER_TYPES } = require(
|
|
5
|
-
const { Logger } = require(
|
|
1
|
+
const cds = require("@sap/cds/lib");
|
|
2
|
+
const { compile: openapi } = require("@cap-js/openapi");
|
|
3
|
+
const { compile: asyncapi } = require("@cap-js/asyncapi");
|
|
4
|
+
const { COMPILER_TYPES } = require("./constants");
|
|
5
|
+
const { Logger } = require("./logger");
|
|
6
|
+
const cdsc = require("@sap/cds-compiler/lib/main");
|
|
6
7
|
|
|
7
|
-
module.exports = async (
|
|
8
|
-
const parts =
|
|
8
|
+
module.exports = async (url, model = null) => {
|
|
9
|
+
const parts = url
|
|
10
|
+
?.split("/")
|
|
11
|
+
.pop()
|
|
12
|
+
.replace(/\.json$/, "")
|
|
13
|
+
.split(".");
|
|
9
14
|
const compilerType = parts.pop();
|
|
10
15
|
const serviceName = parts.join(".");
|
|
11
|
-
const csn = cds.services[serviceName]
|
|
16
|
+
const csn = model || cds.services[serviceName]?.model;
|
|
12
17
|
|
|
13
18
|
let responseFile;
|
|
14
|
-
const options = { service: serviceName, as:
|
|
19
|
+
const options = { service: serviceName, as: "str", messages: [] };
|
|
15
20
|
switch (compilerType) {
|
|
16
21
|
case COMPILER_TYPES.oas3:
|
|
17
22
|
try {
|
|
18
23
|
responseFile = openapi(csn, options);
|
|
19
24
|
} catch (error) {
|
|
20
|
-
Logger.error(
|
|
25
|
+
Logger.error("OpenApi error:", error.message);
|
|
21
26
|
throw error;
|
|
22
27
|
}
|
|
23
28
|
break;
|
|
@@ -25,7 +30,16 @@ module.exports = async (data) => {
|
|
|
25
30
|
try {
|
|
26
31
|
responseFile = asyncapi(csn, options);
|
|
27
32
|
} catch (error) {
|
|
28
|
-
Logger.error(
|
|
33
|
+
Logger.error("AsyncApi error:", error.message);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case COMPILER_TYPES.csn:
|
|
38
|
+
try {
|
|
39
|
+
const opt2 = { beta: { effectiveCsn: true }, effectiveServiceName: serviceName };
|
|
40
|
+
responseFile = cdsc.for.effective(csn, opt2);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
Logger.error("Csn error:", error.message);
|
|
29
43
|
throw error;
|
|
30
44
|
}
|
|
31
45
|
break;
|
|
@@ -33,12 +47,12 @@ module.exports = async (data) => {
|
|
|
33
47
|
try {
|
|
34
48
|
responseFile = await cds.compile(csn).to["edmx"](options);
|
|
35
49
|
} catch (error) {
|
|
36
|
-
Logger.error(
|
|
50
|
+
Logger.error("Edmx error:", error.message);
|
|
37
51
|
throw error;
|
|
38
52
|
}
|
|
39
53
|
}
|
|
40
54
|
return {
|
|
41
55
|
contentType: `application/${compilerType === "edmx" ? "xml" : "json"}`,
|
|
42
|
-
response: responseFile
|
|
56
|
+
response: responseFile,
|
|
43
57
|
};
|
|
44
|
-
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import cds from "@sap/cds";
|
|
2
|
+
import { ord, getMetadata, defaults, authentication, logger } from "./es-module.mjs";
|
|
3
|
+
|
|
4
|
+
export class OpenResourceDiscoveryService extends cds.ApplicationService {
|
|
5
|
+
init() {
|
|
6
|
+
cds.app.get(`${this.path}`, cds.middlewares.before, (_, res) => {
|
|
7
|
+
return res.status(200).send(defaults.baseTemplate);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
cds.app.get(`/ord/v1/documents/ord-document`, authentication.authenticate, async (_, res) => {
|
|
11
|
+
const csn = cds.context?.model || cds.model;
|
|
12
|
+
const data = ord(csn);
|
|
13
|
+
return res.status(200).send(data);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
cds.app.get(`/ord/v1/documents/:id`, authentication.authenticate, async (_, res) => {
|
|
17
|
+
return res.status(404).send("404 Not Found");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
cds.app.get(`/ord/v1/:ordId?/:service?`, authentication.authenticate, async (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const { contentType, response } = await getMetadata(req.url);
|
|
23
|
+
return res.status(200).contentType(contentType).send(response);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.Logger.error(error, "Error while processing the resource definition document");
|
|
26
|
+
return res.status(500).send(error.message);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return super.init();
|
|
31
|
+
}
|
|
32
|
+
}
|