@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 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.2",
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.1",
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.0",
55
- "@cap-js/sqlite": "^2.0.3",
56
- "@sap/cds": "^9.4.3",
57
- "@sap/cds-common-content": "^3.0.1",
58
- "@sap/cds-dk": "^9.4.1",
59
- "eslint": "^9.39.1",
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.0.1",
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.6.2",
70
+ "prettier": "^3.7.4",
67
71
  "shelljs": "^0.10.0"
68
72
  },
69
73
  "cds": {
70
74
  "requires": {
71
- "redis-ratelimit": {
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;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ LocalHTML5Repo: require("./LocalHTML5Repo"),
5
+ };
@@ -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(COMPONENT_NAME).createMainClientAndConnect());
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) {