cds-plugin-ui5 0.1.6 → 0.2.0

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
@@ -3,6 +3,29 @@
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.2.0](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/cds-plugin-ui5@0.1.7...cds-plugin-ui5@0.2.0) (2023-07-31)
7
+
8
+
9
+ ### Features
10
+
11
+ * migration to UI5 Tooling V3 ([#776](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/776)) ([c03bc0e](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/c03bc0e8a8d0b55d38510164c885022e11b597e6))
12
+
13
+
14
+
15
+
16
+
17
+ ## [0.1.7](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/cds-plugin-ui5@0.1.6...cds-plugin-ui5@0.1.7) (2023-07-28)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **cds-plugin-ui5:** allow to select apps or deps ([#775](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/775)) ([aeefb3f](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/aeefb3f5aa70129489f90d684965ade6fc20ef53))
23
+ * **cds-plugin-ui5:** proper dependency handling / decoupling ([#773](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/773)) ([9ef8bf3](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/9ef8bf3da69721a2d599a119407e53c1272fb884))
24
+
25
+
26
+
27
+
28
+
6
29
  ## [0.1.6](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/cds-plugin-ui5@0.1.5...cds-plugin-ui5@0.1.6) (2023-07-11)
7
30
 
8
31
 
package/README.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # cds-plugin-ui5
2
2
 
3
+ > :wave: This is a **community project** and there is no official support for this package! Feel free to use it, open issues, contribute, and help answering questions.
4
+
3
5
  The `cds-plugin-ui5` is a CAP server `cds-plugin` which enables the integration of UI5 tooling based (UI5 freestyle or Fiori elements) projects into the CAP server via the UI5 tooling express middlewares. The UI5 or Fiori elements projects just need to be located in the `app` folder of the CAP server or be dependency of the CAP server.
4
6
 
5
7
  > :construction: **Note**
6
8
  > This cds-plugin is still work in progress and not final yet!
7
9
 
10
+ ## Prerequisites
11
+
12
+ The plugin requires [`@sap/cds`](https://www.npmjs.com/package/@sap/cds) `>=6.8.2`.
13
+
8
14
  ## Usage
9
15
 
10
16
  Add a `devDependency` to the `cds-plugin-ui5` to your CAP server project:
package/cds-plugin.js CHANGED
@@ -1,147 +1,55 @@
1
- const path = require("path");
2
- const fs = require("fs");
3
- const yaml = require("js-yaml");
4
-
5
- const cds = require("@sap/cds");
6
- const { Router } = require("express");
1
+ // @sap/cds/lib/index.js#138: global.cds = cds // REVISIT: using global.cds seems wrong
2
+ const cds = global.cds || require("@sap/cds"); // reuse already loaded cds!
7
3
 
4
+ const log = require("./lib/log");
5
+ const findUI5Modules = require("./lib/findUI5Modules");
6
+ const createPatchedRouter = require("./lib/createPatchedRouter");
8
7
  const applyUI5Middleware = require("./lib/applyUI5Middleware");
9
8
 
10
9
  // marker that the cds-plugin-ui5 plugin is running
11
10
  // to disable the ui5-middleware-cap if used in apps
12
11
  process.env["cds-plugin-ui5"] = true;
13
12
 
14
- /**
15
- * helper to log colorful messages
16
- * @param {string} type the type of the message
17
- * @param {string} message the message text
18
- */
19
- function log(type, message) {
20
- const colors = {
21
- log: "\x1b[0m", // default
22
- info: "\x1b[32m", // green
23
- debug: "\x1b[34m", // blue
24
- warn: "\x1b[33m", // yellow
25
- error: "\x1b[31m", // red
26
- };
27
- if (!console[type]) {
28
- type = "log";
29
- }
30
- console[type](`\x1b[36m[cds-ui5-plugin]\x1b[0m %s[%s]\x1b[0m %s`, colors[type], type, message);
31
- }
32
-
33
13
  cds.on("bootstrap", async function bootstrap(app) {
34
- log("debug", "bootstrap");
14
+ log.debug("bootstrap");
35
15
 
36
- // lookup the app folder to determine local apps and ui5 app directories
37
- const localApps = new Set(),
38
- appDirs = [];
39
- fs.readdirSync(path.join(process.cwd(), "app"), { withFileTypes: true })
40
- .filter((f) => f.isDirectory())
41
- .forEach((d) => localApps.add(d.name));
42
- localApps.forEach((e) => {
43
- const d = path.join(process.cwd(), "app", e);
44
- if (fs.existsSync(path.join(d, "ui5.yaml"))) {
45
- localApps.delete(e);
46
- appDirs.push(d);
47
- }
48
- });
16
+ const cwd = process.cwd();
17
+ const ui5Modules = await findUI5Modules({ cwd });
18
+ const localApps = ui5Modules.localApps;
49
19
 
50
- // look for a single app if no apps were found in the app directories
51
- if(appDirs.length === 0) {
52
- const d = path.join(process.cwd(), "app");
53
- if (fs.existsSync(path.join(d, "ui5.yaml"))) {
54
- appDirs.push(d);
55
- }
56
- }
20
+ const links = [];
57
21
 
58
- // lookup the UI5 dependencies
59
- const pkgJson = require(path.join(process.cwd(), "package.json"));
60
- const deps = [];
61
- deps.push(...Object.keys(pkgJson.dependencies || {}));
62
- deps.push(...Object.keys(pkgJson.devDependencies || {}));
63
- //deps.push(...Object.keys(pkgJson.peerDependencies || {}));
64
- //deps.push(...Object.keys(pkgJson.optionalDependencies || {}));
65
- appDirs.push(
66
- ...deps.filter((dep) => {
67
- try {
68
- require.resolve(`${dep}/ui5.yaml`, {
69
- paths: [process.cwd()],
70
- });
71
- return true;
72
- } catch (e) {
73
- return false;
74
- }
75
- })
76
- );
22
+ // register the UI5 modules via their own router/middlewares
23
+ for await (const ui5Module of ui5Modules) {
24
+ const { mountPath, modulePath } = ui5Module;
77
25
 
78
- // if apps are available, attach the middlewares of the UI5 apps
79
- // to the express of the CAP server via a express router
80
- if (appDirs) {
81
- const links = [];
82
- for await (const appDir of appDirs) {
83
- // read the ui5.yaml file to extract the configuration
84
- const ui5YamlPath = require.resolve(path.join(appDir, "ui5.yaml"), {
85
- paths: [process.cwd()],
86
- });
87
- let ui5Configs;
88
- try {
89
- const content = fs.readFileSync(ui5YamlPath, "utf-8");
90
- ui5Configs = yaml.loadAll(content);
91
- } catch (err) {
92
- if (err.name === "YAMLException") {
93
- log("error", `Failed to read ${ui5YamlPath}!`);
94
- }
95
- throw err;
96
- }
26
+ // mounting the Router for the UI5 application to the CAP server
27
+ log.info(`Mounting ${mountPath} to UI5 app ${modulePath}`);
97
28
 
98
- // by default the mount path is derived from the metadata/name
99
- // and can be overridden by customConfiguration/mountPath
100
- const ui5Config = ui5Configs?.[0];
101
- let mountPath = ui5Config?.customConfiguration?.mountPath || ui5Config?.metadata?.name;
102
- if (!/^\//.test(mountPath)) {
103
- mountPath = `/${mountPath}`; // always start with /
104
- }
105
-
106
- // mounting the Router for the application to the CAP server
107
- log("info", `Mounting ${mountPath} to UI5 app ${appDir}`);
108
- const modulePath = path.dirname(ui5YamlPath);
109
-
110
- // create the router and get rid of the mount path
111
- const router = new Router();
112
- router.use(function (req, res, next) {
113
- // disable the compression when livereload is used
114
- // for loading html-related content (via accept header)
115
- const accept = req.headers["accept"]?.indexOf("html");
116
- if (accept && res._livereload) {
117
- req.headers["accept-encoding"] = "identity";
118
- }
119
- // remove the mount path from the url
120
- req.originalUrl = req.url;
121
- req.baseUrl = "/";
122
- next();
123
- });
29
+ // create a patched router
30
+ const router = await createPatchedRouter();
124
31
 
125
- // apply the UI5 middlewares to the router and
126
- // retrieve the available HTML pages
127
- const pages = await applyUI5Middleware(router, {
128
- basePath: modulePath,
129
- configPath: modulePath,
130
- });
32
+ // apply the UI5 middlewares to the router and
33
+ // retrieve the available HTML pages
34
+ const appInfo = await applyUI5Middleware(router, {
35
+ basePath: modulePath,
36
+ configPath: modulePath,
37
+ });
131
38
 
132
- // append the HTML pages to the links
133
- pages.forEach((page) => {
134
- const prefix = mountPath !== "/" ? mountPath : "";
135
- links.push(`${prefix}${page.getPath()}`);
136
- });
39
+ // register the router to the specified mount path
40
+ app.use(mountPath, router);
137
41
 
138
- // mount the router to the determined mount path
139
- app.use(`${mountPath}`, router);
140
- }
42
+ // append the HTML pages to the links
43
+ appInfo.pages.forEach((page) => {
44
+ const prefix = mountPath !== "/" ? mountPath : "";
45
+ links.push(`${prefix}${page.getPath()}`);
46
+ });
47
+ }
141
48
 
49
+ if (links.length > 0) {
142
50
  // register the custom middleware (similar like in @sap/cds/server.js)
143
51
  app.get("/", function appendLinksToIndex(req, res, next) {
144
- var send = res.send;
52
+ const send = res.send;
145
53
  res.send = function (content) {
146
54
  // the first <ul> element contains the links to the
147
55
  // application pages which is fully under control of
@@ -183,11 +91,11 @@ cds.on("bootstrap", async function bootstrap(app) {
183
91
  ul.innerHTML = newLis.join("\n");
184
92
  content = doc.toString();
185
93
  } else {
186
- log("warn", `Failed to inject application links into CAP index page!`);
94
+ log.warn(`Failed to inject application links into CAP index page!`);
187
95
  }
188
96
  send.apply(this, arguments);
189
97
  };
190
- //log("debug", req.url);
98
+ //log.debug(req.url);
191
99
  next();
192
100
  });
193
101
 
@@ -201,10 +109,10 @@ cds.on("bootstrap", async function bootstrap(app) {
201
109
  if (idxOfServeStatic !== -1) {
202
110
  middlewareStack.splice(idxOfServeStatic, 0, cmw);
203
111
  } else {
204
- log("error", `Failed to determine CAP overview page middleware! You need to manually open the application pages!`);
112
+ log.error(`Failed to determine CAP overview page middleware! You need to manually open the application pages!`);
205
113
  }
206
114
  } else {
207
- log("error", `Failed to inject application pages to CAP overview page! You need to manually open the application pages!`);
115
+ log.error(`Failed to inject application pages to CAP overview page! You need to manually open the application pages!`);
208
116
  }
209
117
  }
210
118
  });
@@ -212,6 +120,6 @@ cds.on("bootstrap", async function bootstrap(app) {
212
120
  // return callback for plugin activation
213
121
  module.exports = {
214
122
  activate: function activate(conf) {
215
- log("debug", "activate", conf);
123
+ log.debug("activate", conf);
216
124
  },
217
125
  };
@@ -1,6 +1,21 @@
1
1
  const path = require("path");
2
2
 
3
+ /**
4
+ * @typedef UI5AppInfo
5
+ * @type {object}
6
+ * @property {Array<string>} pages root path of the module
7
+ */
8
+
3
9
  // inspired by https://github.com/SAP/karma-ui5/blob/main/lib/framework.js#L466-L522
10
+ /**
11
+ * Applies the middlewares for the UI5 application located in the given
12
+ * root directory to the given router.
13
+ * @param {import("express").Router} router Express Router instance
14
+ * @param {object} options configuration options
15
+ * @param {string} options.basePath base path of the UI5 application
16
+ * @param {string} [options.configPath] path to the ui5.yaml (defaults to "${basePath}/ui5.yaml")
17
+ * @returns {UI5AppInfo} UI5 application information object
18
+ */
4
19
  module.exports = async function applyUI5Middleware(router, { basePath, configPath }) {
5
20
  const { graphFromPackageDependencies } = await import("@ui5/project/graph");
6
21
  const { createReaderCollection } = await import("@ui5/fs/resourceFactory");
@@ -41,7 +56,7 @@ module.exports = async function applyUI5Middleware(router, { basePath, configPat
41
56
  };
42
57
 
43
58
  // TODO: rework ui5-server API and make public
44
- const MiddlewareManager = (await import("@ui5/server/internal/MiddlewareManager")).default;
59
+ const { default: MiddlewareManager } = await import("@ui5/server/internal/MiddlewareManager");
45
60
  const middlewareManager = new MiddlewareManager({
46
61
  graph,
47
62
  rootProject,
@@ -54,5 +69,7 @@ module.exports = async function applyUI5Middleware(router, { basePath, configPat
54
69
  });
55
70
  await middlewareManager.applyMiddleware(router);
56
71
 
57
- return await rootReader.byGlob("**/*.html");
72
+ return {
73
+ pages: await rootReader.byGlob("**/*.html"),
74
+ };
58
75
  };
@@ -0,0 +1,24 @@
1
+ const { Router } = require("express");
2
+
3
+ /**
4
+ * Creates a patched router removing the mount path
5
+ * from urls and disabling the encoding
6
+ * @returns {Router} patched router
7
+ */
8
+ module.exports = async function createPatchedRouter() {
9
+ // create the router and get rid of the mount path
10
+ const router = new Router();
11
+ router.use(function (req, res, next) {
12
+ // disable the compression when livereload is used
13
+ // for loading html-related content (via accept header)
14
+ const accept = req.headers["accept"]?.indexOf("html");
15
+ if (accept && res._livereload) {
16
+ req.headers["accept-encoding"] = "identity";
17
+ }
18
+ // remove the mount path from the url
19
+ req.originalUrl = req.url;
20
+ req.baseUrl = "/";
21
+ next();
22
+ });
23
+ return router;
24
+ };
@@ -0,0 +1,109 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const yaml = require("js-yaml");
4
+
5
+ const log = require("./log");
6
+
7
+ /**
8
+ * @typedef UI5Module
9
+ * @type {object}
10
+ * @property {string} modulePath root path of the module
11
+ * @property {string} mountPath path to mount the module to
12
+ */
13
+
14
+ /**
15
+ * Returns all UI5 modules from local apps and the project dependencies.
16
+ * @param {object} options configuration options
17
+ * @param {string} options.cwd current working directory
18
+ * @param {string} options.skipLocalApps skip local apps
19
+ * @param {string} options.skipDeps skip dependencies
20
+ * @returns {Array<UI5Module>} array of UI5 module
21
+ */
22
+ module.exports = async function findUI5Modules({ cwd, skipLocalApps, skipDeps }) {
23
+ // lookup the app folder to determine local apps and UI5 apps
24
+ const localApps = new Set();
25
+ const appDirs = [];
26
+ if (!skipLocalApps) {
27
+ const appDir = path.join(cwd, "app");
28
+ if (fs.existsSync(appDir)) {
29
+ fs.readdirSync(appDir, { withFileTypes: true })
30
+ .filter((f) => f.isDirectory())
31
+ .forEach((d) => localApps.add(d.name));
32
+ localApps.forEach((e) => {
33
+ const d = path.join(appDir, e);
34
+ if (fs.existsSync(path.join(d, "ui5.yaml"))) {
35
+ localApps.delete(e);
36
+ appDirs.push(d);
37
+ }
38
+ });
39
+ }
40
+
41
+ // look for a single app if no apps were found in the app directories
42
+ if (appDirs.length === 0) {
43
+ if (fs.existsSync(path.join(appDir, "ui5.yaml"))) {
44
+ appDirs.push(appDir);
45
+ }
46
+ }
47
+ }
48
+
49
+ // lookup the UI5 modules in the project dependencies
50
+ if (!skipDeps) {
51
+ const pkgJson = require(path.join(cwd, "package.json"));
52
+ const deps = [];
53
+ deps.push(...Object.keys(pkgJson.dependencies || {}));
54
+ deps.push(...Object.keys(pkgJson.devDependencies || {}));
55
+ //deps.push(...Object.keys(pkgJson.peerDependencies || {}));
56
+ //deps.push(...Object.keys(pkgJson.optionalDependencies || {}));
57
+ appDirs.push(
58
+ ...deps.filter((dep) => {
59
+ try {
60
+ require.resolve(`${dep}/ui5.yaml`, {
61
+ paths: [cwd],
62
+ });
63
+ return true;
64
+ } catch (e) {
65
+ return false;
66
+ }
67
+ })
68
+ );
69
+ }
70
+
71
+ // if apps are available, attach the middlewares of the UI5 apps
72
+ // to the express of the CAP server via a express router
73
+ const apps = [];
74
+ if (appDirs) {
75
+ for await (const appDir of appDirs) {
76
+ // read the ui5.yaml file to extract the configuration
77
+ const ui5YamlPath = require.resolve(path.join(appDir, "ui5.yaml"), {
78
+ paths: [cwd],
79
+ });
80
+ let ui5Configs;
81
+ try {
82
+ const content = fs.readFileSync(ui5YamlPath, "utf-8");
83
+ ui5Configs = yaml.loadAll(content);
84
+ } catch (err) {
85
+ if (err.name === "YAMLException") {
86
+ log("error", `Failed to read ${ui5YamlPath}!`);
87
+ }
88
+ throw err;
89
+ }
90
+
91
+ // by default the mount path is derived from the metadata/name
92
+ // and can be overridden by customConfiguration/mountPath
93
+ const ui5Config = ui5Configs?.[0];
94
+ const isApplication = ui5Config?.type === "application";
95
+ if (isApplication) {
96
+ let mountPath = ui5Config?.customConfiguration?.mountPath || ui5Config?.metadata?.name;
97
+ if (!/^\//.test(mountPath)) {
98
+ mountPath = `/${mountPath}`; // always start with /
99
+ }
100
+
101
+ // determine the module path based on the location of the ui5.yaml
102
+ const modulePath = path.dirname(ui5YamlPath);
103
+ apps.push({ modulePath, mountPath });
104
+ }
105
+ }
106
+ }
107
+ apps.localApps = localApps; // necessary for CAP index.html rewrite
108
+ return apps;
109
+ };
package/lib/log.js ADDED
@@ -0,0 +1,26 @@
1
+ const colors = {
2
+ log: "\x1b[0m", // default
3
+ debug: "\x1b[34m", // blue
4
+ info: "\x1b[32m", // green
5
+ warn: "\x1b[33m", // yellow
6
+ error: "\x1b[31m", // red
7
+ };
8
+
9
+ /**
10
+ * helper to log colorful messages
11
+ * @param {string} type the type of the message
12
+ * @param {...string} message the message text
13
+ */
14
+ function log(type, ...message) {
15
+ if (!console[type]) {
16
+ type = "log";
17
+ }
18
+ const args = [`\x1b[36m[cds-ui5-plugin]\x1b[0m %s[%s]\x1b[0m %s`, colors[type], type];
19
+ args.push(message);
20
+ console[type].apply(console[type], args);
21
+ }
22
+
23
+ module.exports = log;
24
+ Object.keys(colors).forEach((level) => {
25
+ module.exports[level] = log.bind(this, level);
26
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cds-plugin-ui5",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "A CAP server cds-plugin to inject the middlewares of all related UI5 tooling based projects.",
5
5
  "author": "Peter Muessig",
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@ui5/fs": "^3.0.4",
14
- "@ui5/project": "^3.4.0",
14
+ "@ui5/project": "^3.4.2",
15
15
  "@ui5/server": "^3.1.3",
16
16
  "js-yaml": "^4.1.0",
17
17
  "node-html-parser": "^6.1.5"
@@ -24,5 +24,5 @@
24
24
  "@sap/cds": ">=6.8.2",
25
25
  "express": ">=4.18.2"
26
26
  },
27
- "gitHead": "1e2a9fb8a38787abd2b2c03d6f9e0536ccdefa69"
27
+ "gitHead": "c6e94b130e72603fc05d70bcaf982bd681b72604"
28
28
  }