diff --git a/packages/cds-plugin-ui5/cds-plugin.js b/packages/cds-plugin-ui5/cds-plugin.js index 803d0486b..7675c3ce4 100644 --- a/packages/cds-plugin-ui5/cds-plugin.js +++ b/packages/cds-plugin-ui5/cds-plugin.js @@ -1,147 +1,55 @@ -const path = require("path"); -const fs = require("fs"); -const yaml = require("js-yaml"); - -const cds = require("@sap/cds"); -const { Router } = require("express"); +// @sap/cds/lib/index.js#138: global.cds = cds // REVISIT: using global.cds seems wrong +const cds = global.cds || require("@sap/cds"); // reuse already loaded cds! +const log = require("./lib/log"); +const findUI5Modules = require("./lib/findUI5Modules"); +const createPatchedRouter = require("./lib/createPatchedRouter"); const applyUI5Middleware = require("./lib/applyUI5Middleware"); // marker that the cds-plugin-ui5 plugin is running // to disable the ui5-middleware-cap if used in apps process.env["cds-plugin-ui5"] = true; -/** - * helper to log colorful messages - * @param {string} type the type of the message - * @param {string} message the message text - */ -function log(type, message) { - const colors = { - log: "\x1b[0m", // default - info: "\x1b[32m", // green - debug: "\x1b[34m", // blue - warn: "\x1b[33m", // yellow - error: "\x1b[31m", // red - }; - if (!console[type]) { - type = "log"; - } - console[type](`\x1b[36m[cds-ui5-plugin]\x1b[0m %s[%s]\x1b[0m %s`, colors[type], type, message); -} - cds.on("bootstrap", async function bootstrap(app) { - log("debug", "bootstrap"); + log.debug("bootstrap"); - // lookup the app folder to determine local apps and ui5 app directories - const localApps = new Set(), - appDirs = []; - fs.readdirSync(path.join(process.cwd(), "app"), { withFileTypes: true }) - .filter((f) => f.isDirectory()) - .forEach((d) => localApps.add(d.name)); - localApps.forEach((e) => { - const d = path.join(process.cwd(), "app", e); - if (fs.existsSync(path.join(d, "ui5.yaml"))) { - localApps.delete(e); - appDirs.push(d); - } - }); + const cwd = process.cwd(); + const ui5Modules = await findUI5Modules({ cwd }); + const localApps = ui5Modules.localApps; - // look for a single app if no apps were found in the app directories - if(appDirs.length === 0) { - const d = path.join(process.cwd(), "app"); - if (fs.existsSync(path.join(d, "ui5.yaml"))) { - appDirs.push(d); - } - } + const links = []; - // lookup the UI5 dependencies - const pkgJson = require(path.join(process.cwd(), "package.json")); - const deps = []; - deps.push(...Object.keys(pkgJson.dependencies || {})); - deps.push(...Object.keys(pkgJson.devDependencies || {})); - //deps.push(...Object.keys(pkgJson.peerDependencies || {})); - //deps.push(...Object.keys(pkgJson.optionalDependencies || {})); - appDirs.push( - ...deps.filter((dep) => { - try { - require.resolve(`${dep}/ui5.yaml`, { - paths: [process.cwd()], - }); - return true; - } catch (e) { - return false; - } - }) - ); + // register the UI5 modules via their own router/middlewares + for await (const ui5Module of ui5Modules) { + const { mountPath, modulePath } = ui5Module; - // if apps are available, attach the middlewares of the UI5 apps - // to the express of the CAP server via a express router - if (appDirs) { - const links = []; - for await (const appDir of appDirs) { - // read the ui5.yaml file to extract the configuration - const ui5YamlPath = require.resolve(path.join(appDir, "ui5.yaml"), { - paths: [process.cwd()], - }); - let ui5Configs; - try { - const content = fs.readFileSync(ui5YamlPath, "utf-8"); - ui5Configs = yaml.loadAll(content); - } catch (err) { - if (err.name === "YAMLException") { - log("error", `Failed to read ${ui5YamlPath}!`); - } - throw err; - } + // mounting the Router for the UI5 application to the CAP server + log.info(`Mounting ${mountPath} to UI5 app ${modulePath}`); - // by default the mount path is derived from the metadata/name - // and can be overridden by customConfiguration/mountPath - const ui5Config = ui5Configs?.[0]; - let mountPath = ui5Config?.customConfiguration?.mountPath || ui5Config?.metadata?.name; - if (!/^\//.test(mountPath)) { - mountPath = `/${mountPath}`; // always start with / - } - - // mounting the Router for the application to the CAP server - log("info", `Mounting ${mountPath} to UI5 app ${appDir}`); - const modulePath = path.dirname(ui5YamlPath); - - // create the router and get rid of the mount path - const router = new Router(); - router.use(function (req, res, next) { - // disable the compression when livereload is used - // for loading html-related content (via accept header) - const accept = req.headers["accept"]?.indexOf("html"); - if (accept && res._livereload) { - req.headers["accept-encoding"] = "identity"; - } - // remove the mount path from the url - req.originalUrl = req.url; - req.baseUrl = "/"; - next(); - }); + // create a patched router + const router = await createPatchedRouter(); - // apply the UI5 middlewares to the router and - // retrieve the available HTML pages - const pages = await applyUI5Middleware(router, { - basePath: modulePath, - configPath: modulePath, - }); + // apply the UI5 middlewares to the router and + // retrieve the available HTML pages + const appInfo = await applyUI5Middleware(router, { + basePath: modulePath, + configPath: modulePath, + }); - // append the HTML pages to the links - pages.forEach((page) => { - const prefix = mountPath !== "/" ? mountPath : ""; - links.push(`${prefix}${page.getPath()}`); - }); + // register the router to the specified mount path + app.use(mountPath, router); - // mount the router to the determined mount path - app.use(`${mountPath}`, router); - } + // append the HTML pages to the links + appInfo.pages.forEach((page) => { + const prefix = mountPath !== "/" ? mountPath : ""; + links.push(`${prefix}${page.getPath()}`); + }); + } + if (links.length > 0) { // register the custom middleware (similar like in @sap/cds/server.js) app.get("/", function appendLinksToIndex(req, res, next) { - var send = res.send; + const send = res.send; res.send = function (content) { // the first