dev-approuter 0.1.0 → 0.1.2
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 +22 -0
- package/lib/devApprouter.js +122 -133
- package/lib/helpers.js +86 -82
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.1.2](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/dev-approuter@0.1.1...dev-approuter@0.1.2) (2023-08-25)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **dev-approuter:** make CAP projects work without reference in xs-dev.json ([#809](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/809)) ([d52a432](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/d52a432609630da3ac215cdcc1a501d7be37655d))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.1.1](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/dev-approuter@0.1.0...dev-approuter@0.1.1) (2023-08-25)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **dev-approuter:** add missing require('fs') ([#808](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/808)) ([0793907](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/07939070bb60f4acb81864b2fd59d2b730f19e7f))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 0.1.0 (2023-08-25)
|
|
7
29
|
|
|
8
30
|
|
package/lib/devApprouter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const approuter = require("@sap/approuter");
|
|
2
2
|
const express = require("express");
|
|
3
|
-
const xsenv = require(
|
|
3
|
+
const xsenv = require("@sap/xsenv");
|
|
4
4
|
|
|
5
5
|
const findUI5Modules = require("cds-plugin-ui5/lib/findUI5Modules");
|
|
6
6
|
const createPatchedRouter = require("cds-plugin-ui5/lib/createPatchedRouter");
|
|
@@ -9,139 +9,128 @@ const applyUI5Middleware = require("cds-plugin-ui5/lib/applyUI5Middleware");
|
|
|
9
9
|
const findCAPModules = require("ui5-middleware-cap/lib/findCAPModules");
|
|
10
10
|
const applyCAPMiddleware = require("ui5-middleware-cap/lib/applyCAPMiddleware");
|
|
11
11
|
|
|
12
|
-
const {
|
|
13
|
-
parseConfig,
|
|
14
|
-
applyDependencyConfig,
|
|
15
|
-
addDestination,
|
|
16
|
-
configureCAPRoute,
|
|
17
|
-
configureUI5Route
|
|
18
|
-
} = require("./helpers");
|
|
19
|
-
|
|
12
|
+
const { parseConfig, applyDependencyConfig, addDestination, configureCAPRoute, configureUI5Route } = require("./helpers");
|
|
20
13
|
|
|
21
14
|
class DevApprouter {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
].concat(extensions)
|
|
142
|
-
});
|
|
143
|
-
console.log(`Approuter started at: http://localhost:${process.env.PORT || 5001}`);
|
|
144
|
-
};
|
|
15
|
+
constructor() {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Starts the dev approuter.
|
|
19
|
+
* Extensions passed as argument are handed to the SAP Approuter without modifications.
|
|
20
|
+
* We suggest to check the documentation (link below) for an extensive read on how the dev approuter works.
|
|
21
|
+
* Here is a shorter summary:
|
|
22
|
+
* UI5 modules declared as (dev)dependencies are added as extensions
|
|
23
|
+
* using the extension API of the SAP Approuter.
|
|
24
|
+
* CAP modules declared as (dev)dependencies are started on a different port.
|
|
25
|
+
* Corresponding routes and destinations are automatically created.
|
|
26
|
+
* A custom `xs-dev.json` can be used to configure the dev approuter,
|
|
27
|
+
* so the productive configuration can be kept in the `xs-app.json`.
|
|
28
|
+
* @param {Object[]} [extensions] - an optional array of extensions that are handed to the SAP Approuter without modification.
|
|
29
|
+
* @param {Object} extensions[].insertMiddleware - an object containing the middlewares.
|
|
30
|
+
* @param {Object[]} extensions[].insertMiddleware.first - an array of middlewares to be inserted in the `first` slot.
|
|
31
|
+
* @param {String} extensions[].insertMiddleware.first[].path - a string representing the path to handle requests for.
|
|
32
|
+
* @param {Function} extensions[].insertMiddleware.first[].handler - a function handling `(req, res, next)`.
|
|
33
|
+
* @param {Object[]} extensions[].insertMiddleware.beforeRequestHandler - an array of middlewares to be inserted in the `beforeRequestHandler` slot.
|
|
34
|
+
* @param {String} extensions[].insertMiddleware.beforeRequestHandler[].path - a string representing the path to handle requests for.
|
|
35
|
+
* @param {Function} extensions[].insertMiddleware.beforeRequestHandler[].handler - a function handling `(req, res, next)`.
|
|
36
|
+
* @param {Object[]} extensions[].insertMiddleware.beforeErrorHandler - an array of middlewares to be inserted in the `beforeErrorHandler` slot.
|
|
37
|
+
* @param {String} extensions[].insertMiddleware.beforeErrorHandler[].path - a string representing the path to handle requests for.
|
|
38
|
+
* @param {Function} extensions[].insertMiddleware.beforeErrorHandler[].handler - a function handling `(req, res, next)`.
|
|
39
|
+
* @see https://github.com/ui5-community/ui5-ecosystem-showcase/tree/main/packages/dev-approuter
|
|
40
|
+
*/
|
|
41
|
+
async start(extensions = []) {
|
|
42
|
+
// loads env from default-env.json
|
|
43
|
+
xsenv.loadEnv();
|
|
44
|
+
|
|
45
|
+
const config = parseConfig();
|
|
46
|
+
const cwd = process.cwd();
|
|
47
|
+
|
|
48
|
+
// lookup the CAP server root
|
|
49
|
+
let capServerConfig;
|
|
50
|
+
const capModules = await findCAPModules({ cwd });
|
|
51
|
+
if (capModules.length > 1) {
|
|
52
|
+
throw new Error(`Multiple CAP modules found. The package dev-approuter can only handle one CAP module as dependency.`);
|
|
53
|
+
} else if (capModules.length === 1) {
|
|
54
|
+
capServerConfig = capModules[0];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// find all UI5 modules from the CAP server root and dependencies from the approuter
|
|
58
|
+
const ui5Modules = [...(await findUI5Modules({ cwd, skipLocalApps: true }))];
|
|
59
|
+
if (capServerConfig) {
|
|
60
|
+
ui5Modules.push(...(await findUI5Modules({ cwd: capServerConfig.modulePath, skipDeps: true })));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// collect UI5 middlewares
|
|
64
|
+
const ui5Middlewares = [];
|
|
65
|
+
for await (const ui5Module of ui5Modules) {
|
|
66
|
+
const { moduleId, modulePath, mountPath } = ui5Module;
|
|
67
|
+
|
|
68
|
+
// create a patched router
|
|
69
|
+
const router = await createPatchedRouter();
|
|
70
|
+
|
|
71
|
+
// apply the UI5 middlewares to the router
|
|
72
|
+
await applyUI5Middleware(router, {
|
|
73
|
+
basePath: modulePath,
|
|
74
|
+
configPath: modulePath,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// mounting the router for the UI5 application to the CAP server
|
|
78
|
+
console.log(`Mounting ${mountPath} to UI5 app ${modulePath}`);
|
|
79
|
+
|
|
80
|
+
let middlewareMountPath;
|
|
81
|
+
// define middlewareMountPath as `/_${mountPath}` if ui5 module is referenced as "dependency" in xs-dev.json or xs-app.json
|
|
82
|
+
if (config.dependencyRoutes && config.dependencyRoutes[moduleId]) {
|
|
83
|
+
// configure UI5 route
|
|
84
|
+
config.dependencyRoutes[moduleId] = configureUI5Route(moduleId, mountPath, config.dependencyRoutes[moduleId]);
|
|
85
|
+
|
|
86
|
+
middlewareMountPath = "/_" + mountPath;
|
|
87
|
+
|
|
88
|
+
// add destination for newly configured route
|
|
89
|
+
addDestination(moduleId, process.env.PORT, middlewareMountPath);
|
|
90
|
+
} else {
|
|
91
|
+
middlewareMountPath = mountPath;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// store the router for later registration
|
|
95
|
+
ui5Middlewares.push({
|
|
96
|
+
path: middlewareMountPath,
|
|
97
|
+
handler: router,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// start CAP server on different port
|
|
102
|
+
if (capServerConfig) {
|
|
103
|
+
const { modulePath, moduleId } = capServerConfig;
|
|
104
|
+
|
|
105
|
+
// start CAP server on different port
|
|
106
|
+
const app = express();
|
|
107
|
+
const { servicesPaths } = await applyCAPMiddleware(app, { root: modulePath, cwd });
|
|
108
|
+
app.listen(process.env.CAP_PORT || 4004, () => {
|
|
109
|
+
console.log(`CAP server started at: http://localhost:${process.env.CAP_PORT || 4004}`);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
config.routes.unshift(configureCAPRoute(moduleId, servicesPaths, config.dependencyRoutes[moduleId]));
|
|
113
|
+
config.dependencyRoutes[moduleId] = configureCAPRoute(moduleId, servicesPaths, config.dependencyRoutes[moduleId]);
|
|
114
|
+
|
|
115
|
+
// add destination for newly configured route
|
|
116
|
+
addDestination(moduleId, process.env.CAP_PORT || 4004);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// create and start the SAP Approuter
|
|
120
|
+
// https://help.sap.com/docs/btp/sap-business-technology-platform/extension-api-of-application-router
|
|
121
|
+
approuter().start({
|
|
122
|
+
port: process.env.PORT || 5000,
|
|
123
|
+
xsappConfig: applyDependencyConfig(config),
|
|
124
|
+
extensions: [
|
|
125
|
+
{
|
|
126
|
+
insertMiddleware: {
|
|
127
|
+
first: ui5Middlewares,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
].concat(extensions),
|
|
131
|
+
});
|
|
132
|
+
console.log(`Approuter started at: http://localhost:${process.env.PORT || 5000}`);
|
|
133
|
+
}
|
|
145
134
|
}
|
|
146
135
|
|
|
147
|
-
module.exports = new DevApprouter();
|
|
136
|
+
module.exports = new DevApprouter();
|
package/lib/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Parses the approuter configuration from an `xs-dev.json` file.
|
|
@@ -8,32 +9,27 @@ const path = require("path");
|
|
|
8
9
|
* @returns {Object} the approuter configuration including all `dependencyRoutes`.
|
|
9
10
|
*/
|
|
10
11
|
const parseConfig = () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
config.dependencyRoutes[`${route.dependency}`] = route;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
return config;
|
|
12
|
+
let config;
|
|
13
|
+
let configFile;
|
|
14
|
+
let configFiles = ["xs-dev.json", "xs-app.json"];
|
|
15
|
+
for (const file of configFiles) {
|
|
16
|
+
if (fs.existsSync(path.join(process.cwd(), file))) {
|
|
17
|
+
config = JSON.parse(fs.readFileSync(path.join(process.cwd(), file), { encoding: "utf8" }));
|
|
18
|
+
configFile = file;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
config.dependencyRoutes = {};
|
|
23
|
+
config.routes?.forEach((route) => {
|
|
24
|
+
if (route.dependency) {
|
|
25
|
+
if (config.dependencyRoutes[`${route.dependency}`]) {
|
|
26
|
+
throw new Error(`Duplicate dependency "${route.dependency}" found in file ${path.join(process.cwd(), configFile)}.`);
|
|
27
|
+
} else {
|
|
28
|
+
config.dependencyRoutes[`${route.dependency}`] = route;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return config;
|
|
37
33
|
};
|
|
38
34
|
|
|
39
35
|
/**
|
|
@@ -42,13 +38,13 @@ const parseConfig = () => {
|
|
|
42
38
|
* @returns {Object} config - the approuter configuration that can be used to start the approuter.
|
|
43
39
|
*/
|
|
44
40
|
const applyDependencyConfig = (config) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
config.routes?.forEach((route) => {
|
|
42
|
+
if (route.dependency) {
|
|
43
|
+
route = config.dependencyRoutes[route.dependency];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
delete config.dependencyRoutes;
|
|
47
|
+
return config;
|
|
52
48
|
};
|
|
53
49
|
|
|
54
50
|
/**
|
|
@@ -59,36 +55,36 @@ const applyDependencyConfig = (config) => {
|
|
|
59
55
|
* @param {String} mountPath - the path the module was mounted to and the destination should point to.
|
|
60
56
|
*/
|
|
61
57
|
const addDestination = (moduleId, port, mountPath) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
let destinations = [];
|
|
59
|
+
if (process.env.destinations) {
|
|
60
|
+
destinations = JSON.parse(process.env.destinations);
|
|
61
|
+
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
let url;
|
|
64
|
+
if (mountPath) {
|
|
65
|
+
url = `http://localhost:${process.env.PORT || 5000}${mountPath}`;
|
|
66
|
+
} else {
|
|
67
|
+
url = `http://localhost:${port}`;
|
|
68
|
+
}
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
70
|
+
// only add new destination if it's not already provided
|
|
71
|
+
const destinationAlreadyExists = destinations.some((destination) => {
|
|
72
|
+
const lowerCaseDestination = {};
|
|
73
|
+
Object.keys(destination).forEach((key) => {
|
|
74
|
+
lowerCaseDestination[key.toLowerCase()] = destination[key];
|
|
75
|
+
});
|
|
76
|
+
return lowerCaseDestination.name === moduleId;
|
|
77
|
+
});
|
|
78
|
+
if (!destinationAlreadyExists) {
|
|
79
|
+
destinations.push({
|
|
80
|
+
Name: moduleId,
|
|
81
|
+
Authentication: "NoAuthentication",
|
|
82
|
+
ProxyType: "Internet",
|
|
83
|
+
Type: "HTTP",
|
|
84
|
+
URL: url,
|
|
85
|
+
});
|
|
86
|
+
process.env.destinations = JSON.stringify(destinations);
|
|
87
|
+
}
|
|
92
88
|
};
|
|
93
89
|
|
|
94
90
|
/**
|
|
@@ -99,38 +95,46 @@ const addDestination = (moduleId, port, mountPath) => {
|
|
|
99
95
|
* @returns {Object} the configured route.
|
|
100
96
|
*/
|
|
101
97
|
const configureCAPRoute = (moduleId, servicesPaths, route) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
if (!route) {
|
|
99
|
+
route = {};
|
|
100
|
+
route.authenticationType = "none";
|
|
101
|
+
}
|
|
102
|
+
route.source = servicesPaths
|
|
103
|
+
.map((path) => {
|
|
104
|
+
return `${path}(.*)`;
|
|
105
|
+
})
|
|
106
|
+
.join("|");
|
|
107
|
+
route.destination = moduleId;
|
|
108
|
+
delete route.dependency;
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
return route;
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
/**
|
|
110
114
|
* Configures the route for a given UI5 module.
|
|
111
|
-
* @param {String} moduleId - the id of the module that the route should be configured for.
|
|
115
|
+
* @param {String} moduleId - the id of the module that the route should be configured for.
|
|
112
116
|
* @param {String} sourcePath - the path the approuter should handle the module at.
|
|
113
117
|
* @param {Object} route - the route that is to be configured.
|
|
114
118
|
* @returns {Object} the configured route.
|
|
115
119
|
*/
|
|
116
120
|
const configureUI5Route = (moduleId, sourcePath, route) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
if (sourcePath === "/") {
|
|
122
|
+
// special regex to avoid endless loop
|
|
123
|
+
route.source = `^(?!.*(/_${sourcePath}))`;
|
|
124
|
+
} else {
|
|
125
|
+
route.source = `^${sourcePath}(.*)$`;
|
|
126
|
+
route.target = "$1";
|
|
127
|
+
}
|
|
128
|
+
route.destination = moduleId;
|
|
129
|
+
delete route.dependency;
|
|
126
130
|
|
|
127
|
-
|
|
131
|
+
return route;
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
module.exports = {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
135
|
+
parseConfig,
|
|
136
|
+
applyDependencyConfig,
|
|
137
|
+
addDestination,
|
|
138
|
+
configureCAPRoute,
|
|
139
|
+
configureUI5Route,
|
|
140
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev-approuter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A dev time wrapper for the SAP Application Router that can serve UI5 and CAP modules added as dependencies.",
|
|
5
5
|
"author": "Nico Schoenteich <nicolai.schoenteich@sap.com> (https://github.com/nicoschoenteich)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
"path": "^0.12.7",
|
|
18
18
|
"ui5-middleware-cap": "^3.1.0"
|
|
19
19
|
},
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "3e8e6d7625a544cb6d1a329cc66a8d796e1c969f"
|
|
21
21
|
}
|