@cap-js-community/common 0.3.2 → 0.3.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/CHANGELOG.md +10 -0
- package/README.md +28 -0
- package/bin/local-html5-repo.js +34 -0
- package/package.json +16 -12
- package/src/index.js +1 -0
- package/src/local-html5-repo/LocalHTML5Repo.js +150 -0
- package/src/local-html5-repo/index.js +5 -0
- package/src/rate-limiting/redis/common.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## Version 0.3.3 - 2026-01-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Local HTML5 repository
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fix rate limiting redis env
|
|
17
|
+
|
|
8
18
|
## Version 0.3.2 - 2025-11-05
|
|
9
19
|
|
|
10
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ This project provides common functionality for CDS services to be consumed with
|
|
|
19
19
|
- [Migration Check](#migration-check)
|
|
20
20
|
- [Rate Limiting](#rate-limiting)
|
|
21
21
|
- [Redis Client](#redis-client)
|
|
22
|
+
- [Local HTML5 Repository](#local-html5-repository)
|
|
22
23
|
- [Support, Feedback, Contributing](#support-feedback-contributing)
|
|
23
24
|
- [Code of Conduct](#code-of-conduct)
|
|
24
25
|
- [Licensing](#licensing)
|
|
@@ -396,6 +397,33 @@ const mainClient = await RedisClient.create().createMainClientAndConnect(options
|
|
|
396
397
|
|
|
397
398
|
For details on Redis `createClient` configuration options see [Redis Client Configuration](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).
|
|
398
399
|
|
|
400
|
+
## Local HTML5 Repository
|
|
401
|
+
|
|
402
|
+
Developing HTML5 apps against hybrid environments including Approuter component requires a local HTML5 repository to directly test the changes to UI5 applications without deployment to a remote HTML5 repository.
|
|
403
|
+
|
|
404
|
+
### Usage
|
|
405
|
+
|
|
406
|
+
- Create a `default-env.json` in `approuter` folder, including a valid HTML5 repository configuration from deployment environment:
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"VCAP_SERVICES": {
|
|
411
|
+
"html5-apps-repo": [
|
|
412
|
+
{
|
|
413
|
+
"credentials": {
|
|
414
|
+
"uri": "https://html5-apps-repo-rt.cfapps.sap.hana.ondemand.com"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- Call command: `local-html5-repo` (shortcut: `lh5r`)
|
|
423
|
+
|
|
424
|
+
All apps and libraries located in `app` folder and containing an `ui5.yaml` are redirected to local HTML5 repository
|
|
425
|
+
served from local file system. All other requests are proxied to the remote HTML5 repository.
|
|
426
|
+
|
|
399
427
|
## Support, Feedback, Contributing
|
|
400
428
|
|
|
401
429
|
This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js-community/common/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).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/* eslint-disable no-console */
|
|
5
|
+
/* eslint-disable n/no-process-exit */
|
|
6
|
+
|
|
7
|
+
const commander = require("commander");
|
|
8
|
+
const program = new commander.Command();
|
|
9
|
+
|
|
10
|
+
const packageJSON = require("../package.json");
|
|
11
|
+
|
|
12
|
+
const { LocalHTML5Repo } = require("../src/local-html5-repo");
|
|
13
|
+
|
|
14
|
+
process.argv = process.argv.map((arg) => {
|
|
15
|
+
return arg.toLowerCase();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.version(packageJSON.version, "-v, --version")
|
|
20
|
+
.usage("[options]")
|
|
21
|
+
.option("-p, --port", "Port of local HTML5 repo");
|
|
22
|
+
|
|
23
|
+
program.unknownOption = function () {};
|
|
24
|
+
program.parse(process.argv);
|
|
25
|
+
|
|
26
|
+
(async () => {
|
|
27
|
+
try {
|
|
28
|
+
const options = program.opts();
|
|
29
|
+
await new LocalHTML5Repo(options).start();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(err);
|
|
32
|
+
process.exit(-1);
|
|
33
|
+
}
|
|
34
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/common",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
"main": "index.js",
|
|
27
27
|
"types": "index.d.ts",
|
|
28
28
|
"bin": {
|
|
29
|
-
"cdsmc": "bin/cdsmc.js"
|
|
29
|
+
"cdsmc": "bin/cdsmc.js",
|
|
30
|
+
"lh5r": "bin/local-html5-repo.js",
|
|
31
|
+
"local-html5-repo": "bin/local-html5-repo.js"
|
|
30
32
|
},
|
|
31
33
|
"scripts": {
|
|
32
34
|
"start": "cds-serve",
|
|
@@ -45,30 +47,32 @@
|
|
|
45
47
|
"audit": "npm audit --only=prod"
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
|
48
|
-
"commander": "^14.0.
|
|
50
|
+
"commander": "^14.0.2",
|
|
51
|
+
"express": "^4.22.1",
|
|
52
|
+
"http-proxy-middleware": "^3.0.5",
|
|
49
53
|
"redis": "^4.7.1",
|
|
50
54
|
"verror": "^1.10.1"
|
|
51
55
|
},
|
|
52
56
|
"devDependencies": {
|
|
53
57
|
"@cap-js-community/common": "./",
|
|
54
|
-
"@cap-js/cds-test": "^0.4.
|
|
55
|
-
"@cap-js/sqlite": "^2.
|
|
56
|
-
"@sap/cds": "^9.
|
|
57
|
-
"@sap/cds-common-content": "^3.0
|
|
58
|
-
"@sap/cds-dk": "^9.
|
|
59
|
-
"eslint": "^9.39.
|
|
58
|
+
"@cap-js/cds-test": "^0.4.1",
|
|
59
|
+
"@cap-js/sqlite": "^2.1.2",
|
|
60
|
+
"@sap/cds": "^9.6.1",
|
|
61
|
+
"@sap/cds-common-content": "^3.1.0",
|
|
62
|
+
"@sap/cds-dk": "^9.6.0",
|
|
63
|
+
"eslint": "^9.39.2",
|
|
60
64
|
"eslint-config-prettier": "^10.1.8",
|
|
61
|
-
"eslint-plugin-jest": "^29.
|
|
65
|
+
"eslint-plugin-jest": "^29.12.1",
|
|
62
66
|
"eslint-plugin-n": "^17.23.1",
|
|
63
67
|
"jest": "^30.2.0",
|
|
64
68
|
"jest-html-reporters": "^3.1.7",
|
|
65
69
|
"jest-junit": "^16.0.0",
|
|
66
|
-
"prettier": "^3.
|
|
70
|
+
"prettier": "^3.7.4",
|
|
67
71
|
"shelljs": "^0.10.0"
|
|
68
72
|
},
|
|
69
73
|
"cds": {
|
|
70
74
|
"requires": {
|
|
71
|
-
"redis-
|
|
75
|
+
"redis-ratelimiting": {
|
|
72
76
|
"vcap": {
|
|
73
77
|
"label": "redis-cache"
|
|
74
78
|
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
+
LocalHTML5Repo: require("./local-html5-repo").LocalHTML5Repo,
|
|
4
5
|
MigrationCheck: require("./migration-check").MigrationCheck,
|
|
5
6
|
RateLimiting: require("./rate-limiting").RateLimiting,
|
|
6
7
|
RedisClient: require("./redis-client").RedisClient,
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
/* eslint-disable n/no-process-exit */
|
|
5
|
+
|
|
6
|
+
// Suppress deprecation warning in Node 22 due to http-proxy using util._extend()
|
|
7
|
+
require("util")._extend = Object.assign;
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const express = require("express");
|
|
12
|
+
const { createProxyMiddleware } = require("http-proxy-middleware");
|
|
13
|
+
|
|
14
|
+
const PORT = process.env.PORT || 3001;
|
|
15
|
+
const DEFAULT_ENV_PATH = path.join(process.cwd(), "approuter/default-env.json");
|
|
16
|
+
const APP_ROOT = path.join(process.cwd(), "app");
|
|
17
|
+
|
|
18
|
+
const COMPONENT_NAME = "/cap-js-community-common/localHTML5Repo";
|
|
19
|
+
|
|
20
|
+
class LocalHTML5Repo {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.component = COMPONENT_NAME;
|
|
23
|
+
this.port = options?.port || PORT;
|
|
24
|
+
this.path = options?.path || DEFAULT_ENV_PATH;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
this.defaultEnv = require(this.path);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`Cannot read default-env.json at ${this.path}`);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
start() {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
this.adjustDefaultEnv();
|
|
37
|
+
|
|
38
|
+
console.log("Registering apps:");
|
|
39
|
+
const app = express();
|
|
40
|
+
|
|
41
|
+
// Serve every webapp
|
|
42
|
+
const appDirectories = fs
|
|
43
|
+
.readdirSync(APP_ROOT, { withFileTypes: true })
|
|
44
|
+
.filter((dirent) => dirent.isDirectory())
|
|
45
|
+
.map((dirent) => dirent.name);
|
|
46
|
+
|
|
47
|
+
for (const appDirectory of appDirectories) {
|
|
48
|
+
const ui5ConfigPath = path.join(APP_ROOT, appDirectory, "ui5.yaml");
|
|
49
|
+
if (!fs.existsSync(ui5ConfigPath)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ui5Config = fs.readFileSync(ui5ConfigPath, "utf-8");
|
|
54
|
+
const { name, type } = this.extractNameAndType(ui5Config);
|
|
55
|
+
const appId = name?.replace(/\./g, "");
|
|
56
|
+
|
|
57
|
+
if (appId) {
|
|
58
|
+
app.use(
|
|
59
|
+
[`/${appId}`, `/${appId}-:version`],
|
|
60
|
+
express.static(path.join(APP_ROOT, appDirectory, type === "application" ? "webapp" : "src")),
|
|
61
|
+
// Serve xs-app.json and other non-webapp files
|
|
62
|
+
express.static(path.join(APP_ROOT, appDirectory), {
|
|
63
|
+
fallthrough: false,
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
console.log(`- ${name} [${type}] -> ${path.join(APP_ROOT, appDirectory)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.on("SIGINT", () => {
|
|
72
|
+
this.stop();
|
|
73
|
+
process.exit(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Forward everything else to the original HTML5 Apps Repo
|
|
77
|
+
const html5RepoProxy = createProxyMiddleware({
|
|
78
|
+
target: this.originalHtmlRepoUrl,
|
|
79
|
+
changeOrigin: true,
|
|
80
|
+
ws: true,
|
|
81
|
+
logLevel: "warn",
|
|
82
|
+
onError(err, req, res) {
|
|
83
|
+
console.error("HTML5 Repo proxy error:", err.message);
|
|
84
|
+
res.status(502).end("Bad Gateway");
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Catch-all proxy (must be last)
|
|
89
|
+
app.use("/", html5RepoProxy);
|
|
90
|
+
|
|
91
|
+
this.server = app.listen(this.port, () => {
|
|
92
|
+
console.log(`Local HTML5 repository running on port ${this.port}`);
|
|
93
|
+
resolve(this.server);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async stop() {
|
|
99
|
+
if (this.server) {
|
|
100
|
+
this.server.close();
|
|
101
|
+
}
|
|
102
|
+
this.restoreDefaultEnv();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
adjustDefaultEnv() {
|
|
106
|
+
this.originalHtmlRepoUrl = this.defaultEnv.VCAP_SERVICES["html5-apps-repo"][0].credentials.uri;
|
|
107
|
+
|
|
108
|
+
this.defaultEnv.VCAP_SERVICES["html5-apps-repo"][0].credentials.uri = `http://localhost:${this.port}`;
|
|
109
|
+
|
|
110
|
+
this.writeDefaultEnv();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
restoreDefaultEnv() {
|
|
114
|
+
this.defaultEnv.VCAP_SERVICES["html5-apps-repo"][0].credentials.uri = this.originalHtmlRepoUrl;
|
|
115
|
+
|
|
116
|
+
this.writeDefaultEnv();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
writeDefaultEnv() {
|
|
120
|
+
const url = this.defaultEnv.VCAP_SERVICES["html5-apps-repo"][0].credentials.uri;
|
|
121
|
+
|
|
122
|
+
console.log(`Rewriting HTML5 Repo URL in default-env.json of approuter: ${url}`);
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(DEFAULT_ENV_PATH, JSON.stringify(this.defaultEnv, null, 2) + "\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
extractNameAndType(content) {
|
|
128
|
+
const result = { name: "", type: "" };
|
|
129
|
+
|
|
130
|
+
for (const line of content.split("\n")) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
|
|
133
|
+
if (trimmed.startsWith("name:")) {
|
|
134
|
+
result.name = trimmed.split(" ")[1];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (trimmed.startsWith("type:")) {
|
|
138
|
+
result.type = trimmed.split(" ")[1];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.name && result.type) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = LocalHTML5Repo;
|
|
@@ -11,7 +11,7 @@ async function connectionCheck() {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function perform(key, cb, cbFallback, retry = cds.env.rateLimiting.retry) {
|
|
14
|
-
const client = cds.env.rateLimiting.redis && (await RedisClient.create(
|
|
14
|
+
const client = cds.env.rateLimiting.redis && (await RedisClient.create("rateLimiting").createMainClientAndConnect());
|
|
15
15
|
if (client) {
|
|
16
16
|
const value = await cb(client, key);
|
|
17
17
|
if (value === undefined) {
|