@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/LICENSE
CHANGED
|
@@ -198,4 +198,4 @@
|
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
[](https://api.reuse.software/info/github.com/cap-js/
|
|
1
|
+
[](https://api.reuse.software/info/github.com/cap-js/ord)
|
|
2
2
|
|
|
3
3
|
# CDS Plugin for ORD
|
|
4
4
|
|
|
5
5
|
## About this project
|
|
6
6
|
|
|
7
|
-
This plugin adds support for the [Open Resource Discovery](https://
|
|
7
|
+
This plugin adds support for the [Open Resource Discovery](https://open-resource-discovery.github.io/specification/) (ORD) protocol for CAP based applications.
|
|
8
8
|
When you add the ORD plugin, your application gains a single entry point, which allows to discover and gather machine-readable information or metadata about the application.
|
|
9
9
|
You can use this information to construct a static metadata catalog or to perform a detailed runtime inspection of your actual system instances / system landscapes.
|
|
10
10
|
|
|
11
|
-
For more information, have a look at the [Open Resource Discovery](https://
|
|
11
|
+
For more information, have a look at the [Open Resource Discovery](https://open-resource-discovery.github.io/specification/) page.
|
|
12
12
|
|
|
13
|
-
> ⚠ By installing this plugin, the metadata describing your CAP application will be made openly accessible.
|
|
14
|
-
>
|
|
15
|
-
> If you have a need to protect your metadata, please refrain from installing this plugin until we support metadata protection (planned).
|
|
13
|
+
> ⚠ By installing this plugin, the metadata describing your CAP application will be made openly accessible. If you want to secure your CAP application's metadata, configure `basic` authentication by setting the environment variables or updating the `.cdsrc.json` file. The plugin prioritizes environment variables, then checks `.cdsrc.json`. If neither is configured, metadata remains publicly accessible.
|
|
16
14
|
|
|
17
15
|
## Requirements and Setup
|
|
18
16
|
|
|
@@ -22,6 +20,84 @@ For more information, have a look at the [Open Resource Discovery](https://sap.g
|
|
|
22
20
|
npm install @cap-js/ord
|
|
23
21
|
```
|
|
24
22
|
|
|
23
|
+
> Note: `@cap-js/openapi` and `@cap-js/asyncapi` packages have been migrated from peerDependencies to dependencies in `package.json`. As a result, using globally installed packages may lead to conflicts. If conflicts arises do `npm uninstall -g @cap-js/openapi @cap-js/asyncapi` and then `npm install` in your project directory.
|
|
24
|
+
|
|
25
|
+
### Authentication
|
|
26
|
+
|
|
27
|
+
To enforce authentication in the ORD Plugin, set the following environment variables:
|
|
28
|
+
|
|
29
|
+
- `ORD_AUTH_TYPE`: Specifies the authentication types.
|
|
30
|
+
- `BASIC_AUTH`: Contains credentials for `basic` authentication.
|
|
31
|
+
|
|
32
|
+
If `ORD_AUTH_TYPE` is not set, the application starts without authentication. This variable accepts `open` and `basic` (UCL-mTLS is also planned).
|
|
33
|
+
|
|
34
|
+
> Note: `open` cannot be combined with `basic` or any other (future) authentication types.
|
|
35
|
+
|
|
36
|
+
#### Open
|
|
37
|
+
|
|
38
|
+
The `open` authentication type bypasses authentication checks.
|
|
39
|
+
|
|
40
|
+
#### Basic Authentication
|
|
41
|
+
|
|
42
|
+
The server supports Basic Authentication through an environment variable that contains a JSON string mapping usernames to bcrypt-hashed passwords:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
BASIC_AUTH='{"admin":"***"}'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Alternatively, configure authentication in `.cdsrc.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
"authentication": {
|
|
52
|
+
"types": ["basic"],
|
|
53
|
+
"credentials": {
|
|
54
|
+
"admin": "***"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To generate bcrypt hashes, use the [htpasswd](https://httpd.apache.org/docs/2.4/programs/htpasswd.html) utility:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
htpasswd -Bnb <user> <password>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This will output something like `admin:$2y$05$...` - use only the hash part (starting with `$2y$`) in your `BASIC_AUTH` JSON.
|
|
66
|
+
|
|
67
|
+
> [!IMPORTANT]
|
|
68
|
+
> Make sure to use strong passwords and handle the BASIC_AUTH environment variable securely. Never commit real credentials or .env files to version control.
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>Using htpasswd in your environment</summary>
|
|
72
|
+
|
|
73
|
+
- **Platform independent**:
|
|
74
|
+
|
|
75
|
+
> Prerequisite is to have [NodeJS](https://nodejs.org/en) installed on the machine.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install -g htpasswd
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
After installing package globally, command `htpasswd` should be available in the Terminal.
|
|
82
|
+
|
|
83
|
+
- **macOS**:
|
|
84
|
+
|
|
85
|
+
Installation of any additional packages is not required. Utility `htpasswd` is available in Terminal by default.
|
|
86
|
+
|
|
87
|
+
- **Linux**:
|
|
88
|
+
|
|
89
|
+
Install apache2-utils package:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Debian/Ubuntu
|
|
93
|
+
sudo apt-get install apache2-utils
|
|
94
|
+
|
|
95
|
+
# RHEL/CentOS
|
|
96
|
+
sudo yum install httpd-tools
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
</details>
|
|
100
|
+
|
|
25
101
|
### Usage
|
|
26
102
|
|
|
27
103
|
#### Programmatic API
|
|
@@ -32,12 +108,26 @@ require("@cap-js/ord");
|
|
|
32
108
|
```
|
|
33
109
|
|
|
34
110
|
```js
|
|
35
|
-
const csn =
|
|
111
|
+
const csn = cds.context?.model || cds.model;
|
|
36
112
|
const ord = cds.compile.to.ord(csn);
|
|
37
113
|
```
|
|
38
114
|
|
|
39
115
|
#### Command Line
|
|
40
116
|
|
|
117
|
+
Build all ord related documents, including ordDocument and services resources files:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
cds build --for ord
|
|
121
|
+
|
|
122
|
+
# By default, it will be generated in /gen/ord dir, e.g.:
|
|
123
|
+
# done > wrote output to:
|
|
124
|
+
# gen/ord/ord-document.json
|
|
125
|
+
# gen/ord/sap.sample:apiResource:AdminService:v1/AdminService.edmx
|
|
126
|
+
# gen/ord/sap.sample:apiResource:AdminService:v1/AdminService.oas3.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Only compile ord document:
|
|
130
|
+
|
|
41
131
|
```sh
|
|
42
132
|
cds compile <path to srv folder> --to ord [-o] [destinationFilePath]
|
|
43
133
|
```
|
|
@@ -47,7 +137,7 @@ cds compile <path to srv folder> --to ord [-o] [destinationFilePath]
|
|
|
47
137
|
#### ORD Endpoints
|
|
48
138
|
|
|
49
139
|
1. Run `cds watch` in the application's root.
|
|
50
|
-
2. Check the following relative paths for ORD information - `/.well-known/open-resource-discovery` , `/
|
|
140
|
+
2. Check the following relative paths for ORD information - `/.well-known/open-resource-discovery` , `/ord/v1/documents/ord-document`.
|
|
51
141
|
|
|
52
142
|
<img width="1300" alt="Sample Application Demo" style="border-radius:0.5rem;" src="./asset/etc/ordEndpoint.gif">
|
|
53
143
|
|
|
@@ -55,6 +145,24 @@ cds compile <path to srv folder> --to ord [-o] [destinationFilePath]
|
|
|
55
145
|
|
|
56
146
|
You can find more information, such as how to customize the ORD Document, in this [document](./docs/ord.md).
|
|
57
147
|
|
|
148
|
+
## How to setup dev environment and run xmpl locally
|
|
149
|
+
|
|
150
|
+
1. **Install dependency**
|
|
151
|
+
```sh
|
|
152
|
+
npm i
|
|
153
|
+
```
|
|
154
|
+
2. **Run xmpl application**
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
cd xmpl/
|
|
158
|
+
|
|
159
|
+
# watch xmpl application
|
|
160
|
+
cds watch
|
|
161
|
+
|
|
162
|
+
# build resources files
|
|
163
|
+
cds build --for ord
|
|
164
|
+
```
|
|
165
|
+
|
|
58
166
|
## Support, Feedback, Contributing
|
|
59
167
|
|
|
60
168
|
This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js/ord/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).
|
package/cds-plugin.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
require("./lib/plugin");
|
|
2
1
|
const cds = require("@sap/cds");
|
|
2
|
+
const { getAuthConfig } = require("./lib/authentication");
|
|
3
|
+
|
|
4
|
+
if (cds.cli.command === "build") {
|
|
5
|
+
cds.build?.register?.("ord", require("./lib/build"));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// load auth config before any service is started
|
|
9
|
+
cds.on("bootstrap", async () => {
|
|
10
|
+
getAuthConfig();
|
|
11
|
+
});
|
|
3
12
|
|
|
4
13
|
function _lazyRegisterCompileTarget() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
const ord = require("./lib/index").ord;
|
|
15
|
+
Object.defineProperty(this, "ord", { ord });
|
|
16
|
+
return ord;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
const registerORDCompileTarget = () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
Object.defineProperty(cds.compile.to, "ord", {
|
|
21
|
+
get: _lazyRegisterCompileTarget,
|
|
22
|
+
configurable: true,
|
|
23
|
+
});
|
|
15
24
|
};
|
|
16
25
|
|
|
17
26
|
registerORDCompileTarget();
|
package/data/well-known.json
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
|
+
const { AUTHENTICATION_TYPE, BASIC_AUTH_HEADER_KEY, AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP } = require("./constants");
|
|
3
|
+
const { Logger } = require("./logger");
|
|
4
|
+
const bcrypt = require("bcrypt");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compares a plain text password with a hashed password
|
|
8
|
+
* @param {string} password Plain text password to check
|
|
9
|
+
* @param {string} hashedPassword Hashed password to compare against
|
|
10
|
+
* @returns {Promise<boolean>} Promise resolving to true if passwords match, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
async function _comparePassword(password, hashedPassword) {
|
|
13
|
+
if (!password || !hashedPassword) {
|
|
14
|
+
throw new Error("Password and hashed password are required");
|
|
15
|
+
}
|
|
16
|
+
return await bcrypt.compare(password, hashedPassword.replace(/^\$2y/, "$2a"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates if a string is a bcrypt hash
|
|
21
|
+
* @param {string} hash String to validate
|
|
22
|
+
* @returns {boolean} boolean indicating if the string is a bcrypt hash
|
|
23
|
+
*/
|
|
24
|
+
function _isBcryptHash(hash) {
|
|
25
|
+
return /^\$2[ayb]\$\d{2}\$[A-Za-z0-9./]{53}$/.test(hash);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create authentication configuration based on data given in the environment variables.
|
|
30
|
+
* @returns {Object} Authentication configuration object or default configuration object as a fallback.
|
|
31
|
+
*/
|
|
32
|
+
function createAuthConfig() {
|
|
33
|
+
const defaultAuthConfig = {
|
|
34
|
+
types: [AUTHENTICATION_TYPE.Open],
|
|
35
|
+
accessStrategies: [{ type: AUTHENTICATION_TYPE.Open }],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const authConfig = {};
|
|
40
|
+
|
|
41
|
+
authConfig.types = process.env.ORD_AUTH_TYPE
|
|
42
|
+
? [...new Set(JSON.parse(process.env.ORD_AUTH_TYPE))]
|
|
43
|
+
: [...new Set(cds.env.authentication?.types)];
|
|
44
|
+
|
|
45
|
+
if (!authConfig.types || authConfig.types.length === 0) {
|
|
46
|
+
Logger.error("createAuthConfig:", 'No authorization type is provided. Defaulting to "Open" authentication');
|
|
47
|
+
return defaultAuthConfig;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (authConfig.types.some((authType) => !Object.values(AUTHENTICATION_TYPE).includes(authType))) {
|
|
51
|
+
return Object.assign(defaultAuthConfig, { error: "Invalid authentication type" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
authConfig.types.includes(AUTHENTICATION_TYPE.Open) &&
|
|
56
|
+
authConfig.types.includes(AUTHENTICATION_TYPE.Basic)
|
|
57
|
+
) {
|
|
58
|
+
return Object.assign(defaultAuthConfig, {
|
|
59
|
+
error: "Open authentication cannot be combined with any other authentication type",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
|
|
64
|
+
const credentials = process.env.BASIC_AUTH
|
|
65
|
+
? JSON.parse(process.env.BASIC_AUTH)
|
|
66
|
+
: cds.env.authentication.credentials;
|
|
67
|
+
|
|
68
|
+
// Check all passwords in credentials map
|
|
69
|
+
for (const [username, password] of Object.entries(credentials)) {
|
|
70
|
+
if (!_isBcryptHash(password)) {
|
|
71
|
+
Logger.error("createAuthConfig:", `Password for user "${username}" must be a bcrypt hash`);
|
|
72
|
+
return Object.assign(defaultAuthConfig, { error: "All passwords must be bcrypt hashes" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Store the complete credentials map
|
|
76
|
+
authConfig.credentials = credentials;
|
|
77
|
+
}
|
|
78
|
+
authConfig.accessStrategies = authConfig.types.map((type) => ({
|
|
79
|
+
type: AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP[type],
|
|
80
|
+
}));
|
|
81
|
+
return authConfig;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return Object.assign(defaultAuthConfig, { error: error.message });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validate, store authentication configuration in cds.context if undefined, and return the context.
|
|
89
|
+
*/
|
|
90
|
+
function getAuthConfig() {
|
|
91
|
+
if (cds.context?.authConfig) return cds.context?.authConfig;
|
|
92
|
+
|
|
93
|
+
const authConfig = createAuthConfig();
|
|
94
|
+
|
|
95
|
+
if (authConfig.error) {
|
|
96
|
+
Logger.error("Authentication configuration error: " + authConfig.error);
|
|
97
|
+
throw new Error("Invalid authentication configuration");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// set the context
|
|
101
|
+
cds.context = {
|
|
102
|
+
authConfig,
|
|
103
|
+
};
|
|
104
|
+
return cds.context?.authConfig;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Middleware to authenticate the request based on the authentication configuration.
|
|
109
|
+
*/
|
|
110
|
+
async function authenticate(req, res, next) {
|
|
111
|
+
const authConfig = cds.context.authConfig;
|
|
112
|
+
|
|
113
|
+
if (authConfig.types.includes(AUTHENTICATION_TYPE.Open)) {
|
|
114
|
+
res.status(200);
|
|
115
|
+
return next();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
!Object.keys(req.headers).includes(BASIC_AUTH_HEADER_KEY) &&
|
|
120
|
+
authConfig.types.includes(AUTHENTICATION_TYPE.Basic)
|
|
121
|
+
) {
|
|
122
|
+
return res.status(401).setHeader("WWW-Authenticate", 'Basic realm="401"').send("Authentication required.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (req.headers[BASIC_AUTH_HEADER_KEY] && authConfig.types.includes(AUTHENTICATION_TYPE.Basic)) {
|
|
126
|
+
const authHeader = req.headers[BASIC_AUTH_HEADER_KEY];
|
|
127
|
+
|
|
128
|
+
if (!authHeader.startsWith("Basic ")) {
|
|
129
|
+
return res.status(401).send("Invalid authentication type");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const [username, password] = Buffer.from(authHeader.split(" ")[1], "base64").toString().split(":");
|
|
133
|
+
const credentials = authConfig.credentials;
|
|
134
|
+
const storedPassword = credentials[username];
|
|
135
|
+
|
|
136
|
+
if (storedPassword && (await _comparePassword(password, storedPassword))) {
|
|
137
|
+
res.status(200);
|
|
138
|
+
return next();
|
|
139
|
+
} else {
|
|
140
|
+
return res.status(401).send("Invalid credentials");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// In other cases, the request is unauthorized.
|
|
145
|
+
// TODO: Add support for UCL-mTLS authorization.
|
|
146
|
+
return res.status(401).send("Not authorized");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
authenticate,
|
|
151
|
+
createAuthConfig,
|
|
152
|
+
getAuthConfig,
|
|
153
|
+
};
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const cds = require("@sap/cds");
|
|
2
|
+
const cds_dk = require("@sap/cds-dk");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const { ord, getMetadata } = require("./index");
|
|
6
|
+
const { BUILD_DEFAULT_PATH, ORD_SERVICE_NAME, ORD_DOCUMENT_FILE_NAME } = require("./constants");
|
|
7
|
+
|
|
8
|
+
module.exports = class OrdBuildPlugin extends cds_dk.build.Plugin {
|
|
9
|
+
static taskDefaults = { src: cds.env.folders.srv };
|
|
10
|
+
|
|
11
|
+
init() {
|
|
12
|
+
this.task.dest = path.join(cds.root, BUILD_DEFAULT_PATH);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async _writeResourcesFiles(resObj, model, promises) {
|
|
16
|
+
for (const resource of resObj) {
|
|
17
|
+
if (resource.ordId.includes(ORD_SERVICE_NAME) || !resource.resourceDefinitions) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for (const resourceDefinition of resource.resourceDefinitions) {
|
|
22
|
+
const url = resourceDefinition.url;
|
|
23
|
+
const fileName = path.join(resource.ordId, url.split("/").pop());
|
|
24
|
+
try {
|
|
25
|
+
const { _, response } = await getMetadata(url, model); // eslint-disable-line no-unused-vars
|
|
26
|
+
promises.push(
|
|
27
|
+
this.write(response)
|
|
28
|
+
.to(fileName.replace(/:/g, "_"))
|
|
29
|
+
.catch((err) => {
|
|
30
|
+
console.log("Error", `Failed to write file ${fileName}: ${err.message}`);
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log("Error", `Failed to get metadata for ${url}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
postProcess(ordDocument) {
|
|
41
|
+
const clonedOrdDocument = _.cloneDeep(ordDocument);
|
|
42
|
+
const _updateResourceUrls = (resources) => {
|
|
43
|
+
for (const resource of resources || []) {
|
|
44
|
+
if (resource.resourceDefinitions) {
|
|
45
|
+
for (const resourceDefinition of resource.resourceDefinitions) {
|
|
46
|
+
let url = resourceDefinition.url;
|
|
47
|
+
url = this._createRelativePath(url);
|
|
48
|
+
resourceDefinition.url = url;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
_updateResourceUrls(clonedOrdDocument.apiResources);
|
|
55
|
+
_updateResourceUrls(clonedOrdDocument.eventResources);
|
|
56
|
+
|
|
57
|
+
return clonedOrdDocument;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_createRelativePath(url) {
|
|
61
|
+
let relative = url.split("/ord/v1").pop();
|
|
62
|
+
if (relative.startsWith("/")) relative = relative.slice(1);
|
|
63
|
+
relative = relative.replace(/:/g, "_");
|
|
64
|
+
return path.join(...relative.split("/"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async build() {
|
|
68
|
+
const model = await this.model();
|
|
69
|
+
const ordDocument = ord(model);
|
|
70
|
+
const postProcessedOrdDocument = this.postProcess(ordDocument);
|
|
71
|
+
|
|
72
|
+
const promises = [];
|
|
73
|
+
promises.push(this.write(postProcessedOrdDocument).to(ORD_DOCUMENT_FILE_NAME));
|
|
74
|
+
|
|
75
|
+
if (ordDocument.apiResources && ordDocument.apiResources.length > 0) {
|
|
76
|
+
await this._writeResourcesFiles(ordDocument.apiResources, model, promises);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (ordDocument.eventResources && ordDocument.eventResources.length > 0) {
|
|
80
|
+
await this._writeResourcesFiles(ordDocument.eventResources, model, promises);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Promise.all(promises);
|
|
84
|
+
}
|
|
85
|
+
};
|
package/lib/constants.js
CHANGED
|
@@ -1,39 +1,115 @@
|
|
|
1
|
+
const AUTHENTICATION_TYPE = Object.freeze({
|
|
2
|
+
Open: "open",
|
|
3
|
+
Basic: "basic",
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const BASIC_AUTH_HEADER_KEY = "authorization";
|
|
7
|
+
|
|
8
|
+
const BUILD_DEFAULT_PATH = "gen/ord";
|
|
9
|
+
|
|
10
|
+
const BLOCKED_SERVICE_NAME = Object.freeze({
|
|
11
|
+
MTXServices: "cds.xt.MTXServices",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const ORD_ACCESS_STRATEGY = Object.freeze({
|
|
15
|
+
Open: "open",
|
|
16
|
+
Basic: "sap.businesshub:basic-auth:v1",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP = Object.freeze({
|
|
20
|
+
[AUTHENTICATION_TYPE.Open]: ORD_ACCESS_STRATEGY.Open,
|
|
21
|
+
[AUTHENTICATION_TYPE.Basic]: ORD_ACCESS_STRATEGY.Basic,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const CDS_ELEMENT_KIND = Object.freeze({
|
|
25
|
+
action: "action",
|
|
26
|
+
annotation: "annotation",
|
|
27
|
+
context: "context",
|
|
28
|
+
function: "function",
|
|
29
|
+
entity: "entity",
|
|
30
|
+
event: "event",
|
|
31
|
+
service: "service",
|
|
32
|
+
type: "type",
|
|
33
|
+
});
|
|
34
|
+
|
|
1
35
|
const COMPILER_TYPES = Object.freeze({
|
|
2
36
|
oas3: "oas3",
|
|
3
37
|
asyncapi2: "asyncapi2",
|
|
4
38
|
edmx: "edmx",
|
|
39
|
+
csn: "csn",
|
|
5
40
|
});
|
|
6
41
|
|
|
7
42
|
const CONTENT_MERGE_KEY = "ordId";
|
|
8
43
|
|
|
44
|
+
const DATA_PRODUCT_ANNOTATION = "@DataIntegration.dataProduct.type";
|
|
45
|
+
|
|
46
|
+
const DATA_PRODUCT_TYPE = Object.freeze({
|
|
47
|
+
primary: "primary",
|
|
48
|
+
});
|
|
49
|
+
|
|
9
50
|
const DESCRIPTION_PREFIX = "Description for ";
|
|
10
51
|
|
|
52
|
+
const ENTITY_RELATIONSHIP_ANNOTATION = "@EntityRelationship.entityType";
|
|
53
|
+
|
|
54
|
+
const LEVEL = Object.freeze({
|
|
55
|
+
aggregate: "aggregate",
|
|
56
|
+
rootEntity: "root-entity",
|
|
57
|
+
subEntity: "sub-entity",
|
|
58
|
+
});
|
|
59
|
+
|
|
11
60
|
const OPEN_RESOURCE_DISCOVERY_VERSION = "1.9";
|
|
12
61
|
|
|
13
62
|
const ORD_EXTENSIONS_PREFIX = "@ORD.Extensions.";
|
|
14
63
|
|
|
64
|
+
const ORD_ODM_ENTITY_NAME_ANNOTATION = "@ODM.entityName";
|
|
65
|
+
|
|
66
|
+
const ORD_EXISTING_PRODUCT_PROPERTY = "existingProductORDId";
|
|
67
|
+
|
|
15
68
|
const ORD_RESOURCE_TYPE = Object.freeze({
|
|
16
|
-
service: "service",
|
|
17
|
-
entity: "entity",
|
|
18
|
-
event: "event",
|
|
19
69
|
api: "api",
|
|
70
|
+
event: "event",
|
|
71
|
+
integrationDependency: "integrationDependency",
|
|
72
|
+
entityType: "entityType",
|
|
20
73
|
});
|
|
21
74
|
|
|
75
|
+
const ORD_SERVICE_NAME = "OpenResourceDiscoveryService";
|
|
76
|
+
|
|
77
|
+
const ORD_DOCUMENT_FILE_NAME = "ord-document.json";
|
|
78
|
+
|
|
22
79
|
const RESOURCE_VISIBILITY = Object.freeze({
|
|
23
80
|
public: "public",
|
|
24
81
|
internal: "internal",
|
|
25
82
|
private: "private",
|
|
26
83
|
});
|
|
27
84
|
|
|
28
|
-
const SHORT_DESCRIPTION_PREFIX = "Short description
|
|
85
|
+
const SHORT_DESCRIPTION_PREFIX = "Short description of ";
|
|
86
|
+
|
|
87
|
+
const SEM_VERSION_REGEX =
|
|
88
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
29
89
|
|
|
30
90
|
module.exports = {
|
|
91
|
+
AUTHENTICATION_TYPE,
|
|
92
|
+
AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP,
|
|
93
|
+
BASIC_AUTH_HEADER_KEY,
|
|
94
|
+
BUILD_DEFAULT_PATH,
|
|
95
|
+
BLOCKED_SERVICE_NAME,
|
|
96
|
+
CDS_ELEMENT_KIND,
|
|
31
97
|
COMPILER_TYPES,
|
|
32
98
|
CONTENT_MERGE_KEY,
|
|
99
|
+
DATA_PRODUCT_ANNOTATION,
|
|
100
|
+
DATA_PRODUCT_TYPE,
|
|
33
101
|
DESCRIPTION_PREFIX,
|
|
102
|
+
ENTITY_RELATIONSHIP_ANNOTATION,
|
|
103
|
+
LEVEL,
|
|
34
104
|
OPEN_RESOURCE_DISCOVERY_VERSION,
|
|
105
|
+
ORD_ACCESS_STRATEGY,
|
|
106
|
+
ORD_DOCUMENT_FILE_NAME,
|
|
35
107
|
ORD_EXTENSIONS_PREFIX,
|
|
108
|
+
ORD_ODM_ENTITY_NAME_ANNOTATION,
|
|
109
|
+
ORD_EXISTING_PRODUCT_PROPERTY,
|
|
36
110
|
ORD_RESOURCE_TYPE,
|
|
111
|
+
ORD_SERVICE_NAME,
|
|
37
112
|
RESOURCE_VISIBILITY,
|
|
38
113
|
SHORT_DESCRIPTION_PREFIX,
|
|
114
|
+
SEM_VERSION_REGEX,
|
|
39
115
|
};
|
package/lib/date.js
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
function getRFC3339Date(
|
|
1
|
+
function getRFC3339Date() {
|
|
2
2
|
const now = new Date();
|
|
3
3
|
const year = now.getUTCFullYear();
|
|
4
|
-
const month = String(now.getUTCMonth() + 1).padStart(2,
|
|
5
|
-
const day = String(now.getUTCDate()).padStart(2,
|
|
6
|
-
const hours = String(now.getUTCHours()).padStart(2,
|
|
7
|
-
const minutes = String(now.getUTCMinutes()).padStart(2,
|
|
8
|
-
const seconds = String(now.getUTCSeconds()).padStart(2,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const offsetSign = '+';
|
|
14
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`;
|
|
15
|
-
} else {
|
|
16
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`;
|
|
17
|
-
}
|
|
4
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
5
|
+
const day = String(now.getUTCDate()).padStart(2, "0");
|
|
6
|
+
const hours = String(now.getUTCHours()).padStart(2, "0");
|
|
7
|
+
const minutes = String(now.getUTCMinutes()).padStart(2, "0");
|
|
8
|
+
const seconds = String(now.getUTCSeconds()).padStart(2, "0");
|
|
9
|
+
const offsetHours = "01";
|
|
10
|
+
const offsetMinutes = "00";
|
|
11
|
+
const offsetSign = "+";
|
|
12
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`;
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
module.exports = {
|
|
21
|
-
getRFC3339Date
|
|
16
|
+
getRFC3339Date,
|
|
22
17
|
};
|