• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / karma-ui5 / 15176548114

22 May 2025 02:12AM UTC coverage: 54.927% (-0.6%) from 55.544%
15176548114

Pull #728

github

web-flow
Merge bb28e7ffa into d1c932c39
Pull Request #728: Add middleware to support Maven-managed dependencies on Fiori Pipeline

199 of 360 branches covered (55.28%)

Branch coverage included in aggregate %.

5 of 16 new or added lines in 1 file covered. (31.25%)

1 existing line in 1 file now uncovered.

364 of 665 relevant lines covered (54.74%)

10.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.1
/lib/framework.js
1
import {parse as parseUrl} from "node:url";
2
import http from "node:http";
3
import https from "node:https";
4
import httpProxy from "http-proxy";
5
import {statSync, readFileSync, existsSync} from "node:fs";
6
import path from "node:path";
7
import {fileURLToPath} from "node:url";
8
import yaml from "js-yaml";
9
import {Router} from "express";
10
import {ErrorMessage} from "./errors.js";
11
const __dirname = path.dirname(fileURLToPath(import.meta.url));
62✔
12

13
export default class Framework {
14
        constructor() {
15
                this.config = {};
70✔
16
        }
17

18
        createPluginFilesPattern(pattern) {
19
                return {pattern, included: true, served: true, watched: false};
52✔
20
        }
21

22
        createProjectFilesPattern(pattern) {
23
                return {pattern, included: false, served: true, watched: true};
49✔
24
        }
25

26
        /**
27
         * Checks if a list of paths exists
28
         *
29
         * @private
30
         * @param {Array} paths List of paths to check
31
         *
32
         * @returns {boolean[]} array if path exist
33
         */
34
        pathsExist(paths) {
35
                return paths.map((folderName) => this.exists(path.join(this.config.basePath, folderName)));
107✔
36
        }
37

38
        /**
39
         * Checks if a file or path exists
40
         *
41
         * @private
42
         * @param {string} filePath Path to check
43
         * @returns {boolean} true if the file or path exists
44
         */
45
        exists(filePath) {
46
                try {
15✔
47
                        return statSync(filePath).isDirectory();
15✔
48
                } catch (err) {
49
                        // "File or directory does not exist"
50
                        if (err.code === "ENOENT") {
12!
51
                                return false;
12✔
52
                        } else {
53
                                throw err;
×
54
                        }
55
                }
56
        }
57

58
        /**
59
         * Mutates config and auto set type if not defined
60
         */
61
        detectTypeFromFolder() {
62
                const webappFolder = this.config.ui5.paths.webapp;
31✔
63
                const srcFolder = this.config.ui5.paths.src;
31✔
64
                const testFolder = this.config.ui5.paths.test;
31✔
65
                const [hasWebapp, hasSrc, hasTest] = this.pathsExist([webappFolder, srcFolder, testFolder]);
31✔
66
                if (hasWebapp) return "application";
31✔
67
                if (hasSrc && hasTest) return "library";
4!
68
        }
69

70
        replaceLast(path, replacement) {
71
                return path.split("/").slice(0, -1).concat(replacement).join("/");
45✔
72
        }
73

74
        checkLegacy(config) {
75
                if (config.openui5 || config.client.openui5) {
68✔
76
                        this.logger.log("error", ErrorMessage.migrateConfig());
1✔
77
                        throw new Error(ErrorMessage.failure());
1✔
78
                }
79
        }
80

81
        initScriptMode(config) {
82
                let url;
83
                if (config.ui5.url) {
5✔
84
                        url = config.ui5.url + "/resources/sap-ui-core.js";
2✔
85
                } else {
86
                        // Uses middleware if no url has been specified
87
                        // Need to use an absolute URL as the file doesn't exist physically but will be
88
                        // resolved via our middleware
89
                        url = `${config.protocol}//${config.hostname}:${config.port}/base/`;
3✔
90
                        if (config.ui5.type === "application") {
3✔
91
                                url += `${config.ui5.paths.webapp}/resources/sap-ui-core.js`;
2✔
92
                        } else if (config.ui5.type === "library") {
1!
93
                                url += `${this.replaceLast(config.ui5.paths.src, "resources")}/sap-ui-core.js`;
1✔
94
                        }
95
                }
96
                config.client.ui5.config = config.ui5.config;
5✔
97
                config.client.ui5.tests = config.ui5.tests;
5✔
98
                if (config.ui5.tests) {
5✔
99
                        config.files.unshift(this.createPluginFilesPattern(`${__dirname}/client/autorun.js`));
1✔
100
                }
101
                config.files.unshift(this.createPluginFilesPattern(url));
5✔
102
                config.files.unshift(this.createPluginFilesPattern(`${__dirname}/client/sap-ui-config.js`));
5✔
103
        }
104

105
        async init({config, logger}) {
106
                this.config = config;
68✔
107
                this.logger = logger.create("ui5.framework");
68✔
108
                this.config.basePath = config.basePath || "";
68✔
109
                this.config.client = config.client || {};
68✔
110
                this.config.client.clearContext = false;
68✔
111
                // Always override client ui5 config. It should not be used by consumers.
112
                // Relevant options (e.g. testpage, config, tests) will be written to the client section.
113
                this.config.client.ui5 = {};
68✔
114
                this.config.client.ui5.useIframe = true; // for now only allow using iframes in HTML mode
68✔
115
                this.config.ui5 = config.ui5 || {};
68✔
116
                this.config.middleware = config.middleware || [];
68✔
117
                this.config.files = config.files || [];
68✔
118
                this.config.beforeMiddleware = config.beforeMiddleware || [];
68✔
119
                this.config.reporters = this.config.reporters || [];
68✔
120

121
                if (!this.config.ui5.mode) {
68✔
122
                        this.config.ui5.mode = "html";
60✔
123
                }
124
                if (typeof this.config.ui5.failOnEmptyTestPage === "undefined") {
68✔
125
                        // TODO 3.0: Enable by default
126
                        this.config.ui5.failOnEmptyTestPage = false;
63✔
127
                }
128

129
                this.checkLegacy(config);
68✔
130

131
                if (this.config.ui5.mode && ["script", "html"].indexOf(this.config.ui5.mode) === -1) {
67✔
132
                        this.logger.log("error", ErrorMessage.invalidMode(this.config.ui5.mode));
1✔
133
                        throw new Error(ErrorMessage.failure());
1✔
134
                }
135

136
                const incompatibleFrameworks = ["qunit", "sinon"];
66✔
137
                const hasIncompatibleFrameworks =
138
                        (frameworks) => frameworks.some((fwk) => incompatibleFrameworks.includes(fwk));
66✔
139
                if (this.config.ui5.mode === "html" && hasIncompatibleFrameworks(this.config.frameworks || [])) {
66✔
140
                        this.logger.log("error", ErrorMessage.incompatibleFrameworks(this.config.frameworks) );
2✔
141
                        throw new Error(ErrorMessage.failure());
2✔
142
                }
143

144
                if (this.config.ui5.mode === "html" && this.config.files.length > 0) {
64✔
145
                        this.logger.log("error", ErrorMessage.containsFilesDefinition() );
1✔
146
                        throw new Error(ErrorMessage.failure());
1✔
147
                }
148

149
                if (this.config.ui5.paths && !this.config.ui5.type) {
63✔
150
                        this.logger.log("error", ErrorMessage.customPathWithoutType() );
1✔
151
                        throw new Error(ErrorMessage.failure());
1✔
152
                }
153

154
                if (this.config.ui5.mode !== "html" && this.config.ui5.urlParameters) {
62✔
155
                        this.logger.log("error", ErrorMessage.urlParametersConfigInNonHtmlMode(this.config.ui5.mode,
1✔
156
                                this.config.ui5.urlParameters));
157
                        throw new Error(ErrorMessage.failure());
1✔
158
                }
159

160
                if (this.config.ui5.urlParameters !== undefined && !Array.isArray(this.config.ui5.urlParameters)) {
61✔
161
                        this.logger.log("error", ErrorMessage.urlParametersNotAnArray(this.config.ui5.urlParameters));
1✔
162
                        throw new Error(ErrorMessage.failure());
1✔
163
                }
164

165
                if (this.config.ui5.urlParameters) {
60✔
166
                        this.config.ui5.urlParameters.forEach((urlParameter) => {
3✔
167
                                if (typeof urlParameter !== "object") {
6✔
168
                                        this.logger.log("error", ErrorMessage.urlParameterNotObject(urlParameter));
1✔
169
                                        throw new Error(ErrorMessage.failure());
1✔
170
                                }
171
                                if (urlParameter.key === undefined || urlParameter.value === undefined) {
5✔
172
                                        this.logger.log("error", ErrorMessage.urlParameterMissingKeyOrValue(urlParameter));
1✔
173
                                        throw new Error(ErrorMessage.failure());
1✔
174
                                }
175
                        });
176
                }
177

178
                if (this.config.ui5.urlParameters !== undefined && !Array.isArray(this.config.ui5.urlParameters)) {
58!
179
                        this.logger.log("error", ErrorMessage.urlParametersNotAnArray(this.config.ui5.urlParameters));
×
180
                        throw new Error(ErrorMessage.failure());
×
181
                }
182

183
                if (typeof this.config.ui5.failOnEmptyTestPage !== "boolean") {
58✔
184
                        this.logger.log(
2✔
185
                                "error",
186
                                ErrorMessage.failOnEmptyTestPageNotTypeBoolean(this.config.ui5.failOnEmptyTestPage)
187
                        );
188
                        throw new Error(ErrorMessage.failure());
2✔
189
                }
190
                if (this.config.ui5.mode !== "html" && this.config.ui5.failOnEmptyTestPage === true) {
56✔
191
                        this.logger.log("error", ErrorMessage.failOnEmptyTestPageInNonHtmlMode(this.config.ui5.mode));
1✔
192
                        throw new Error(ErrorMessage.failure());
1✔
193
                }
194

195
                if (this.config.reporters.includes("ui5--fileExport")) {
55✔
196
                        this.logger.log("error", ErrorMessage.invalidFileExportReporterUsage());
1✔
197
                        throw new Error(ErrorMessage.failure());
1✔
198
                }
199
                if (this.config.ui5.fileExport === true || typeof this.config.ui5.fileExport === "object") {
54✔
200
                        this.config.reporters.push("ui5--fileExport");
2✔
201
                        if (this.config.ui5.fileExport === true) {
2✔
202
                                this.config.ui5.fileExport = {};
1✔
203
                        }
204
                }
205

206
                this.config.ui5.paths = this.config.ui5.paths || {
54✔
207
                        webapp: "webapp",
208
                        src: "src",
209
                        test: "test"
210
                };
211

212
                ["webapp", "src", "test"].forEach((pathName) => {
54✔
213
                        let pathValue = this.config.ui5.paths[pathName];
158✔
214
                        if (!pathValue) {
158✔
215
                                return;
12✔
216
                        }
217

218
                        let absolutePathValue;
219
                        const absoluteBasePath = path.resolve(this.config.basePath);
146✔
220

221
                        // Make sure paths are relative to the basePath
222
                        if (path.isAbsolute(pathValue)) {
146✔
223
                                absolutePathValue = pathValue;
4✔
224
                                pathValue = path.relative(this.config.basePath, pathValue);
4✔
225
                        } else {
226
                                absolutePathValue = path.resolve(this.config.basePath, pathValue);
142✔
227
                        }
228

229
                        // Paths must be within basePath
230
                        if (!absolutePathValue.startsWith(absoluteBasePath)) {
146✔
231
                                this.logger.log("error", ErrorMessage.pathNotWithinBasePath({
2✔
232
                                        pathName,
233
                                        pathValue: this.config.ui5.paths[pathName], // use value given in config here
234
                                        absolutePathValue,
235
                                        basePath: absoluteBasePath
236
                                }));
237
                                throw new Error(ErrorMessage.failure());
2✔
238
                        }
239

240
                        this.config.ui5.paths[pathName] = pathValue;
144✔
241
                });
242

243
                this.autoDetectType();
52✔
244

245
                if (this.config.ui5.mode === "script") {
46✔
246
                        this.initScriptMode(config);
5✔
247
                } else {
248
                        // Add browser bundle including third-party dependencies
249
                        this.config.files.unshift(this.createPluginFilesPattern(__dirname + "/../dist/browser-bundle.js"));
41✔
250
                }
251

252
                // Make testpage url available to the client
253
                this.config.client.ui5.testpage = this.config.ui5.testpage;
46✔
254
                // Make failOnEmptyTestPage option available to the client
255
                this.config.client.ui5.failOnEmptyTestPage = this.config.ui5.failOnEmptyTestPage;
46✔
256
                // Pass configured urlParameters to client
257
                this.config.client.ui5.urlParameters = this.config.ui5.urlParameters;
46✔
258
                // Pass fileExport parameter to client
259
                this.config.client.ui5.fileExport = this.config.reporters.includes("ui5--fileExport");
46✔
260

261

262
                if (this.config.ui5.type === "application") {
46✔
263
                        const webappFolder = this.config.ui5.paths.webapp;
38✔
264
                        if (!this.exists(path.join(this.config.basePath, webappFolder))) {
38✔
265
                                this.logger.log("error", ErrorMessage.applicationFolderNotFound(webappFolder));
1✔
266
                                throw new Error(ErrorMessage.failure());
1✔
267
                        }
268

269
                        // Match all files (including dotfiles)
270
                        this.config.files.push(
37✔
271
                                this.createProjectFilesPattern(config.basePath + `/{${webappFolder}/**,${webappFolder}/**/.*}`)
272
                        );
273
                } else if (config.ui5.type === "library") {
8✔
274
                        const srcFolder = this.config.ui5.paths.src;
7✔
275
                        const testFolder = this.config.ui5.paths.test;
7✔
276

277
                        const [hasSrc, hasTest] = this.pathsExist([srcFolder, testFolder]);
7✔
278
                        if (!hasSrc || !hasTest) {
7✔
279
                                this.logger.log("error", ErrorMessage.libraryFolderNotFound({
1✔
280
                                        srcFolder, testFolder, hasSrc, hasTest
281
                                }));
282
                                throw new Error(ErrorMessage.failure());
1✔
283
                        }
284

285
                        this.config.files.push(
6✔
286
                                // Match all files (including dotfiles)
287
                                this.createProjectFilesPattern(`${config.basePath}/{${srcFolder}/**,${srcFolder}/**/.*}`),
288
                                this.createProjectFilesPattern(`${config.basePath}/{${testFolder}/**,${testFolder}/**/.*}`),
289
                        );
290
                } else {
291
                        this.logger.log("error", ErrorMessage.invalidProjectType(config.ui5.type) );
1✔
292
                        throw new Error(ErrorMessage.failure());
1✔
293
                }
294

295
                // this.addPreprocessor();
296
                await this.setupMiddleware();
43✔
297
                return this;
43✔
298
        }
299

300
        autoDetectType() {
301
                if (this.config.ui5.type) {
52✔
302
                        return;
15✔
303
                }
304
                const {ui5: {configPath}, basePath} = this.config;
37✔
305
                const filePath = configPath ?
37✔
306
                        path.resolve(basePath, configPath) : path.join(basePath, "ui5.yaml");
307
                let fileContent;
308
                try {
37✔
309
                        fileContent = readFileSync(filePath);
37✔
310
                } catch (err) {
311
                        if (err.code !== "ENOENT") {
31!
312
                                throw err;
×
313
                        }
314
                }
315

316
                if (fileContent) {
37✔
317
                        let configs;
318
                        try {
6✔
319
                                configs = yaml.loadAll(fileContent, {
6✔
320
                                        filename: filePath
321
                                });
322
                        } catch (err) {
323
                                if (err.name === "YAMLException") {
1!
324
                                        this.logger.log("error", ErrorMessage.invalidUI5Yaml({
1✔
325
                                                filePath, yamlException: err
326
                                        }));
327
                                        throw Error(ErrorMessage.failure());
1✔
328
                                } else {
329
                                        throw err;
×
330
                                }
331
                        }
332

333
                        if (!configs[0] || !configs[0].type) {
5✔
334
                                this.logger.log("error", ErrorMessage.missingTypeInYaml());
1✔
335
                                throw Error(ErrorMessage.failure());
1✔
336
                        }
337

338
                        this.config.ui5.type = configs[0].type;
4✔
339
                } else {
340
                        this.config.ui5.type = this.detectTypeFromFolder();
31✔
341
                }
342
                if (!this.config.ui5.type) {
35✔
343
                        let errorText = "";
4✔
344

345
                        if (this.config.basePath.endsWith("/webapp")) {
4✔
346
                                errorText = ErrorMessage.invalidBasePath();
1✔
347
                        } else {
348
                                errorText = ErrorMessage.invalidFolderStructure();
3✔
349
                        }
350

351
                        this.logger.log("error", errorText);
4✔
352
                        throw new Error(ErrorMessage.failure());
4✔
353
                }
354
        }
355

356
        // Adding coverage preprocessor is currently not supported
357
        // /**
358
        //  * Adds preprocessors dynamically in case if no preprocessors have been defined in the config
359
        //  */
360
        // addPreprocessor() {
361
        //         const type = this.config.ui5.type,
362
        //                 cwd = process.cwd(),
363
        //                 srcFolder = this.config.ui5.paths.src,
364
        //                 webappFolder = this.config.ui5.paths.webapp;
365

366
        //         if (this.config.preprocessors && type && Object.keys(this.config.preprocessors).length === 0) {
367
        //                 if (type === "library") {
368
        //                         this.config.preprocessors[`${cwd}/${srcFolder}/**/*.js`] = ['coverage'];
369
        //                 } else if (type === "application") {
370
        //                         this.config.preprocessors[`${cwd}/{${webappFolder},${webappFolder}/!(test)}/*.js`] = ['coverage'];
371
        //                 }
372
        //         }
373
        // }
374

375
        /**
376
         * Rewrites the given url to use a virtual path that can be resolved
377
         * by the UI5 Tooling middleware or to conform with the UI5 CDN.
378
         *
379
         * Example (application):
380
         * /base/webapp/resources/sap-ui-core.js -> /resources/sap-ui-core.js
381
         *
382
         * Example (library):
383
         * /base/src/resources/sap-ui-core.js -> /resources/sap-ui-core.js
384
         * /base/test/test-resources/sap-ui-core.js -> /test-resources/sap-ui-core.js
385
         *
386
         * @param {string} url
387
         * @returns {string}
388
         */
389
        rewriteUrl(url) {
390
                const type = this.config.ui5.type;
27✔
391
                const webappFolder = this.config.ui5.paths.webapp;
27✔
392
                const srcFolder = this.config.ui5.paths.src;
27✔
393
                const testFolder = this.config.ui5.paths.test;
27✔
394
                if (!type) {
27✔
395
                        // TODO: do we want no type to be allowed?
396
                        return url;
6✔
397
                } else if (type === "application") {
21✔
398
                        const webappPattern = new RegExp(`^/base/${webappFolder}/`);
10✔
399
                        if (webappPattern.test(url)) {
10✔
400
                                return url.replace(webappPattern, "/");
3✔
401
                        }
402
                } else if (type === "library") {
11✔
403
                        const srcPattern = new RegExp(`^/base/${srcFolder}/`);
10✔
404
                        const testPattern = new RegExp(`^/base/${testFolder}/`);
10✔
405
                        // const basePattern = /^\/base\//; // TODO: is this expected?
406
                        if (srcPattern.test(url)) {
10✔
407
                                return url.replace(srcPattern, "/resources/");
1✔
408
                        } else if (testPattern.test(url)) {
9✔
409
                                return url.replace(testPattern, "/test-resources/");
1✔
410
                        } /* else if (basePattern.test(url)) {
411
                                return url.replace(basePattern, "/");
412
                        }*/
413
                } else {
414
                        this.logger.log("error", ErrorMessage.urlRewriteFailed(type));
1✔
415
                        return;
1✔
416
                }
417

418
                return url;
15✔
419
        }
420

421
        /**
422
         * Rewrites the given url from a virtual path (resources / test-resources)
423
         * to a filesystem path so that the request can be handled by the karma
424
         * middleware that serves the project files.
425
         *
426
         * This is only relevant for type "library", as it has two separate folders (src / test).
427
         *
428
         * Example:
429
         * /base/resources/sap-ui-core.js -> /base/src/sap-ui-core.js
430
         * /base/test-resources/sap/ui/test/ -> /base/test/sap/ui/test/
431
         *
432
         * @param {string} url
433
         * @returns {string}
434
         */
435
        rewriteUrlBefore(url) {
436
                const type = this.config.ui5.type;
23✔
437
                if (type !== "library") {
23✔
438
                        // Only rewrite "before" for type library
439
                        return url;
12✔
440
                }
441
                const srcFolder = this.config.ui5.paths.src;
11✔
442
                const testFolder = this.config.ui5.paths.test;
11✔
443
                const resourcesSrcPattern = new RegExp(
11✔
444
                        `/base/${this.replaceLast(srcFolder, "resources")}/`
445
                );
446
                const resourcesTestPattern = new RegExp(
11✔
447
                        `/base/${this.replaceLast(testFolder, "resources")}/`
448
                );
449
                const testResourcesSrcPattern = new RegExp(
11✔
450
                        `/base/${this.replaceLast(srcFolder, "test-resources")}/`
451
                );
452
                const testResourcesTestPattern = new RegExp(
11✔
453
                        `/base/${this.replaceLast(testFolder, "test-resources")}/`
454
                );
455
                if (resourcesSrcPattern.test(url)) {
11✔
456
                        return url.replace(resourcesSrcPattern, `/base/${srcFolder}/`);
2✔
457
                } else if (resourcesTestPattern.test(url)) {
9✔
458
                        return url.replace(resourcesTestPattern, `/base/${srcFolder}/`);
1✔
459
                } else if (testResourcesSrcPattern.test(url)) {
8✔
460
                        return url.replace(testResourcesSrcPattern, `/base/${testFolder}/`);
2✔
461
                } else if (testResourcesTestPattern.test(url)) {
6✔
462
                        return url.replace(testResourcesTestPattern, `/base/${testFolder}/`);
1✔
463
                }
464
                return url;
5✔
465
        }
466

467
        async applyUI5Middleware(router, {basePath, configPath}) {
468
                const graphOptions = {
2✔
469
                        cwd: basePath
470
                };
471
                if (configPath) {
2✔
472
                        graphOptions.rootConfigPath = path.resolve(basePath, configPath);
1✔
473
                }
474

475
                const {graphFromPackageDependencies} = await import("@ui5/project/graph");
2✔
476
                const {createReaderCollection} = await import("@ui5/fs/resourceFactory");
2✔
477
                const ReaderCollectionPrioritized = (await import("@ui5/fs/ReaderCollectionPrioritized")).default;
2✔
478

479
                const graph = await graphFromPackageDependencies(graphOptions);
2✔
480

481
                const rootProject = graph.getRoot();
2✔
482

483
                const readers = [];
2✔
484
                await graph.traverseBreadthFirst(async function({project: dep}) {
2✔
485
                        if (dep.getName() === rootProject.getName()) {
4✔
486
                                // Ignore root project
487
                                return;
2✔
488
                        }
489
                        readers.push(dep.getReader({style: "runtime"}));
2✔
490
                });
491

492
                const dependencies = createReaderCollection({
2✔
493
                        name: `Dependency reader collection for project ${rootProject.getName()}`,
494
                        readers
495
                });
496

497
                const rootReader = rootProject.getReader({style: "runtime"});
2✔
498

499
                const combo = new ReaderCollectionPrioritized({
2✔
500
                        name: "server - prioritize workspace over dependencies",
501
                        readers: [rootReader, dependencies]
502
                });
503
                const resources = {
2✔
504
                        rootProject: rootReader,
505
                        dependencies: dependencies,
506
                        all: combo
507
                };
508

509
                const MiddlewareManager = (await import("@ui5/server/internal/MiddlewareManager")).default;
2✔
510
                const middlewareManager = new MiddlewareManager({
2✔
511
                        graph,
512
                        rootProject,
513
                        resources,
514
                });
515

516
                await middlewareManager.applyMiddleware(router);
2✔
517
        }
518

519
        async setupUI5Server({basePath, configPath}) {
520
                const router = new Router();
33✔
521
                await this.applyUI5Middleware(router, {basePath, configPath});
33✔
522
                return router;
33✔
523
        }
524

525
        setupProxy({url}) {
526
                const {protocol} = parseUrl(url);
9✔
527
                const Agent = protocol === "https:" ? https.Agent : http.Agent;
9✔
528
                const agent = new Agent({keepAlive: true});
9✔
529
                const proxy = httpProxy.createProxyServer({
9✔
530
                        target: url,
531
                        changeOrigin: true,
532
                        agent
533
                });
534
                proxy.on("error", (err, req /* , res*/) => {
9✔
535
                        this.logger.warn(`Failed to proxy ${req.url} (${err.code}: ${err.message})`);
×
536
                });
537
                return (req, res) => proxy.web(req, res);
9✔
538
        }
539

540
        beforeMiddlewareRewriteUrl(req, res, next) {
541
                req.url = this.rewriteUrlBefore(req.url);
1✔
542
                next();
1✔
543
        }
544

545
        middlewareRewriteUrl(req, res, next) {
546
                req.url = this.rewriteUrl(req.url);
1✔
547
                next();
1✔
548
        }
549
        /**
550
         * For Maven-managed UI5 libraries (fiori pipeline), dependencies are extracted to the
551
         * target/dependency/META-INF/resources directory inside the project.  This middleware
552
         * returns the file contents if found inside the dependency directory.
553
         *
554
         * @param {http.ClientRequest} req Client request object
555
         * @param {http.ServerResponse} res Server response object
556
         * @param {Function} next Next middleware
557
         * @returns {void}
558
         */
559
        middlewareMavenDependencies(req, res, next) {
560
                const sMvnDependencyDir = path.resolve("./target/dependency/META-INF/resources");
1✔
561
                if (req.method !== "GET" || !existsSync(sMvnDependencyDir)) {
1!
562
                        next();
1✔
563
                        return;
1✔
564
                }
NEW
565
                const oReqUrl = parseUrl(req.url);
×
NEW
566
                const sPath = oReqUrl.pathname;
×
567
                // This executes after beforeMiddlewareRewriteUrl, so paths have been remapped
568
                // from /base/resources to /resources
NEW
569
                if (sPath.startsWith("/resources")) {
×
NEW
570
                        const sSearchPath = path.resolve(sMvnDependencyDir + sPath.substring("/resources".length));
×
NEW
571
                        if (existsSync(sSearchPath)) {
×
NEW
572
                                const oBody = readFileSync(sSearchPath);
×
NEW
573
                                res.write(oBody);
×
NEW
574
                                res.statusCode = 200;
×
NEW
575
                                res.end();
×
NEW
576
                                return;
×
577
                        }
578
                }
NEW
UNCOV
579
                next();
×
580
        }
581
        async setupMiddleware() {
582
                const config = this.config;
43✔
583

584
                if (config.ui5.type === "library") {
43✔
585
                        config.ui5._beforeMiddleware = new Router();
6✔
586
                        config.ui5._beforeMiddleware.use(this.beforeMiddlewareRewriteUrl.bind(this));
6✔
587
                        config.beforeMiddleware.push("ui5--beforeMiddleware");
6✔
588
                }
589

590
                let middleware;
591
                if (config.ui5.url) {
43✔
592
                        middleware = this.setupProxy(config.ui5);
7✔
593
                } else if (config.ui5.useMiddleware !== false) {
36✔
594
                        middleware = await this.setupUI5Server({
33✔
595
                                basePath: config.basePath,
596
                                configPath: config.ui5.configPath
597
                        });
598
                }
599

600
                if (middleware) {
43✔
601
                        config.ui5._middleware = new Router();
40✔
602
                        config.ui5._middleware.use(this.middlewareMavenDependencies.bind(this));
40✔
603
                        config.ui5._middleware.use(this.middlewareRewriteUrl.bind(this));
40✔
604
                        config.ui5._middleware.use(middleware);
40✔
605
                        config.middleware.push("ui5--middleware");
40✔
606
                }
607
        }
608
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc