@sap-ux/preview-middleware 0.23.9 → 0.23.11

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/README.md CHANGED
@@ -177,8 +177,17 @@ When the middleware is used in an adaptation project together with a middleware
177
177
  afterMiddleware: preview-middleware
178
178
  ```
179
179
 
180
+ ### [Mobile Device Preview](#mobile-device-preview)
181
+ The preview middleware supports previewing applications on physical mobile devices, enabling developers to test their applications on real mobile devices directly from Visual Studio Code or SAP Business Application Studio.
182
+
183
+ Using the `--accept-remote-connections` argument, a remote URL that can be accessed from mobile devices on the same network will be logged in the console, and a QR code will be displayed for easy access.
184
+
185
+ ```Json
186
+ "start-mobile": "ui5 serve --open test/flp.html#app-preview --accept-remote-connections"
187
+ ```
188
+
180
189
  ### [Programmatic Usage](#programmatic-usage)
181
- Alternatively you can use the underlying middleware fuction programmatically, e.g. for the case when you want to incorporate the `preview-middleware` functionality in your own middleware.
190
+ Alternatively you can use the underlying middleware function programmatically. This is useful when you want to incorporate the `preview-middleware` functionality in your own middleware.
182
191
 
183
192
  ```typescript
184
193
  import { FlpSandbox } from '@sap-ux/preview-middleware';
@@ -1,3 +1,4 @@
1
1
  export { FlpSandbox, initAdp } from './flp';
2
2
  export { generatePreviewFiles, getPreviewPaths, sanitizeRtaConfig } from './config';
3
+ export { logRemoteUrl, isRemoteConnectionsEnabled } from './remote-url';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeRtaConfig = exports.getPreviewPaths = exports.generatePreviewFiles = exports.initAdp = exports.FlpSandbox = void 0;
3
+ exports.isRemoteConnectionsEnabled = exports.logRemoteUrl = exports.sanitizeRtaConfig = exports.getPreviewPaths = exports.generatePreviewFiles = exports.initAdp = exports.FlpSandbox = void 0;
4
4
  var flp_1 = require("./flp");
5
5
  Object.defineProperty(exports, "FlpSandbox", { enumerable: true, get: function () { return flp_1.FlpSandbox; } });
6
6
  Object.defineProperty(exports, "initAdp", { enumerable: true, get: function () { return flp_1.initAdp; } });
@@ -8,4 +8,7 @@ var config_1 = require("./config");
8
8
  Object.defineProperty(exports, "generatePreviewFiles", { enumerable: true, get: function () { return config_1.generatePreviewFiles; } });
9
9
  Object.defineProperty(exports, "getPreviewPaths", { enumerable: true, get: function () { return config_1.getPreviewPaths; } });
10
10
  Object.defineProperty(exports, "sanitizeRtaConfig", { enumerable: true, get: function () { return config_1.sanitizeRtaConfig; } });
11
+ var remote_url_1 = require("./remote-url");
12
+ Object.defineProperty(exports, "logRemoteUrl", { enumerable: true, get: function () { return remote_url_1.logRemoteUrl; } });
13
+ Object.defineProperty(exports, "isRemoteConnectionsEnabled", { enumerable: true, get: function () { return remote_url_1.isRemoteConnectionsEnabled; } });
11
14
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,33 @@
1
+ import type { ToolsLogger } from '@sap-ux/logger';
2
+ /**
3
+ * Log remote URL for mobile device access.
4
+ *
5
+ * @param logger ToolsLogger instance
6
+ */
7
+ export declare function logRemoteUrl(logger: ToolsLogger): Promise<void>;
8
+ /**
9
+ * Get the remote URL for mobile device access.
10
+ *
11
+ * @param logger ToolsLogger instance
12
+ * @returns The remote URL or undefined if not available
13
+ */
14
+ export declare function getRemoteUrl(logger: ToolsLogger): Promise<string | undefined>;
15
+ /**
16
+ * Check if remote connections should be enabled based on command line arguments.
17
+ *
18
+ * @returns Whether --accept-remote-connections is present in process arguments
19
+ */
20
+ export declare function isRemoteConnectionsEnabled(): boolean;
21
+ /**
22
+ * Extract port from command line arguments or environment.
23
+ *
24
+ * @returns The port number if found
25
+ */
26
+ export declare function getPortFromArgs(): number | undefined;
27
+ /**
28
+ * Extract open path from command line arguments.
29
+ *
30
+ * @returns The path from --open or -o parameter if found
31
+ */
32
+ export declare function getOpenPathFromArgs(): string | undefined;
33
+ //# sourceMappingURL=remote-url.d.ts.map
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logRemoteUrl = logRemoteUrl;
7
+ exports.getRemoteUrl = getRemoteUrl;
8
+ exports.isRemoteConnectionsEnabled = isRemoteConnectionsEnabled;
9
+ exports.getPortFromArgs = getPortFromArgs;
10
+ exports.getOpenPathFromArgs = getOpenPathFromArgs;
11
+ const btp_utils_1 = require("@sap-ux/btp-utils");
12
+ const os_1 = require("os");
13
+ const bas_sdk_1 = require("@sap/bas-sdk");
14
+ const qrcode_1 = __importDefault(require("qrcode"));
15
+ /**
16
+ * Log remote URL for mobile device access.
17
+ *
18
+ * @param logger ToolsLogger instance
19
+ */
20
+ async function logRemoteUrl(logger) {
21
+ try {
22
+ const remoteUrl = await getRemoteUrl(logger);
23
+ const generateQRCode = async (text) => {
24
+ try {
25
+ const qrString = await qrcode_1.default.toString(text, { type: 'terminal', small: true });
26
+ logger.info(qrString);
27
+ }
28
+ catch (err) {
29
+ logger.error(err);
30
+ }
31
+ };
32
+ if (remoteUrl) {
33
+ logger.info(`Remote URL: ${remoteUrl}`);
34
+ logger.info('Scan the QR code below with your mobile device to access the preview:');
35
+ await generateQRCode(remoteUrl);
36
+ }
37
+ }
38
+ catch (error) {
39
+ logger.debug(`Could not generate remote URL: ${error.message}`);
40
+ }
41
+ }
42
+ /**
43
+ * Get the remote URL for mobile device access.
44
+ *
45
+ * @param logger ToolsLogger instance
46
+ * @returns The remote URL or undefined if not available
47
+ */
48
+ async function getRemoteUrl(logger) {
49
+ try {
50
+ if ((0, btp_utils_1.isAppStudio)()) {
51
+ return await getBASRemoteUrl(logger);
52
+ }
53
+ else {
54
+ return getIdeRemoteUrl(logger);
55
+ }
56
+ }
57
+ catch (error) {
58
+ logger.error(`Failed to generate remote URL: ${error.message}`);
59
+ return undefined;
60
+ }
61
+ }
62
+ /**
63
+ * Get remote URL for BAS environment using BAS SDK.
64
+ *
65
+ * @param logger ToolsLogger instance instance
66
+ * @returns The remote URL from BAS SDK
67
+ */
68
+ async function getBASRemoteUrl(logger) {
69
+ try {
70
+ const devspaceInfo = await bas_sdk_1.devspace.getDevspaceInfo();
71
+ if (devspaceInfo?.url) {
72
+ const port = getPortFromArgs() ?? 8080;
73
+ const baseUrl = bas_sdk_1.devspace.getAppExternalUri(`http://localhost:${port}`);
74
+ const devspaceUrl = baseUrl.replace(/(\/|%2F)$/, '');
75
+ const remoteUrl = appendOpenPath(devspaceUrl, getOpenPathFromArgs());
76
+ logger.debug(`BAS remote URL generated: ${remoteUrl}`);
77
+ return remoteUrl;
78
+ }
79
+ logger.warn('Could not retrieve BAS remote URL from devspace info');
80
+ return undefined;
81
+ }
82
+ catch (error) {
83
+ logger.error(`Failed to get BAS remote URL: ${error.message}`);
84
+ return undefined;
85
+ }
86
+ }
87
+ /**
88
+ * Get remote URL for the local IDE environment by detecting network IP.
89
+ * Only works if --accept-remote-connections is enabled.
90
+ *
91
+ * @param logger ToolsLogger instance
92
+ * @returns The remote URL based on network IP
93
+ */
94
+ function getIdeRemoteUrl(logger) {
95
+ try {
96
+ const networkIP = getNetworkIP();
97
+ if (!networkIP) {
98
+ logger.warn('Could not determine network IP address for remote access');
99
+ return undefined;
100
+ }
101
+ const protocol = 'http';
102
+ const port = getPortFromArgs() ?? 8080;
103
+ const baseUrl = `${protocol}://${networkIP}:${port}`;
104
+ const remoteUrl = appendOpenPath(baseUrl, getOpenPathFromArgs());
105
+ logger.debug(`IDE remote URL generated: ${remoteUrl}`);
106
+ return remoteUrl;
107
+ }
108
+ catch (error) {
109
+ logger.error(`Failed to generate IDE remote URL: ${error.message}`);
110
+ return undefined;
111
+ }
112
+ }
113
+ /**
114
+ * Get the first available network IP address (excluding localhost).
115
+ *
116
+ * @returns The network IP address
117
+ */
118
+ function getNetworkIP() {
119
+ const interfaces = (0, os_1.networkInterfaces)();
120
+ // Priority order: prefer non-internal IPv4 addresses
121
+ for (const interfaceName of Object.keys(interfaces)) {
122
+ const networkInterface = interfaces[interfaceName];
123
+ if (!networkInterface) {
124
+ continue;
125
+ }
126
+ for (const alias of networkInterface) {
127
+ // Skip internal (localhost) addresses and IPv6
128
+ if (alias.family === 'IPv4' && !alias.internal) {
129
+ return alias.address;
130
+ }
131
+ }
132
+ }
133
+ return undefined;
134
+ }
135
+ /**
136
+ * Append the open path to a base URL if provided.
137
+ *
138
+ * @param baseUrl The base URL
139
+ * @param openPath The path to append
140
+ * @returns The complete URL with path appended
141
+ */
142
+ function appendOpenPath(baseUrl, openPath) {
143
+ if (!openPath) {
144
+ return baseUrl;
145
+ }
146
+ // Ensure the path starts with a forward slash
147
+ const normalizedPath = openPath.startsWith('/') ? openPath : `/${openPath}`;
148
+ return `${baseUrl}${normalizedPath}`;
149
+ }
150
+ /**
151
+ * Check if remote connections should be enabled based on command line arguments.
152
+ *
153
+ * @returns Whether --accept-remote-connections is present in process arguments
154
+ */
155
+ function isRemoteConnectionsEnabled() {
156
+ return process.argv.includes('--accept-remote-connections');
157
+ }
158
+ /**
159
+ * Extract port from command line arguments or environment.
160
+ *
161
+ * @returns The port number if found
162
+ */
163
+ function getPortFromArgs() {
164
+ // Check for --port argument
165
+ const portIndex = process.argv.findIndex((arg) => arg === '--port' || arg.startsWith('--port='));
166
+ if (portIndex !== -1) {
167
+ const portArg = process.argv[portIndex];
168
+ if (portArg.includes('=')) {
169
+ // --port=8080 format
170
+ const port = parseInt(portArg.split('=')[1], 10);
171
+ return isNaN(port) ? undefined : port;
172
+ }
173
+ else if (portIndex + 1 < process.argv.length) {
174
+ // --port 8080 format
175
+ const port = parseInt(process.argv[portIndex + 1], 10);
176
+ return isNaN(port) ? undefined : port;
177
+ }
178
+ }
179
+ // Check environment variable
180
+ const envPort = process.env.PORT;
181
+ if (envPort) {
182
+ const port = parseInt(envPort, 10);
183
+ return isNaN(port) ? undefined : port;
184
+ }
185
+ return undefined;
186
+ }
187
+ /**
188
+ * Extract open path from command line arguments.
189
+ *
190
+ * @returns The path from --open or -o parameter if found
191
+ */
192
+ function getOpenPathFromArgs() {
193
+ // Check for --open or -o argument
194
+ const openIndex = process.argv.findIndex((arg) => arg === '--open' || arg === '-o' || arg.startsWith('--open=') || arg.startsWith('--o='));
195
+ if (openIndex !== -1) {
196
+ const openArg = process.argv[openIndex];
197
+ if (openArg.includes('=')) {
198
+ // --open=path or -o=path format
199
+ return openArg.split('=')[1];
200
+ }
201
+ else if (openIndex + 1 < process.argv.length) {
202
+ // --open path or -o path format
203
+ return process.argv[openIndex + 1];
204
+ }
205
+ }
206
+ return undefined;
207
+ }
208
+ //# sourceMappingURL=remote-url.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './ui5/middleware';
2
- export { FlpSandbox, initAdp, generatePreviewFiles, getPreviewPaths, sanitizeRtaConfig } from './base';
2
+ export { FlpSandbox, initAdp, generatePreviewFiles, getPreviewPaths, sanitizeRtaConfig, logRemoteUrl, isRemoteConnectionsEnabled } from './base';
3
3
  export { FlpConfig, RtaConfig, TestConfig, MiddlewareConfig, DefaultFlpPath, DefaultIntent, TestConfigDefaults } from './types';
4
4
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.sanitizeRtaConfig = exports.getPreviewPaths = exports.generatePreviewFiles = exports.initAdp = exports.FlpSandbox = void 0;
17
+ exports.isRemoteConnectionsEnabled = exports.logRemoteUrl = exports.sanitizeRtaConfig = exports.getPreviewPaths = exports.generatePreviewFiles = exports.initAdp = exports.FlpSandbox = void 0;
18
18
  __exportStar(require("./ui5/middleware"), exports);
19
19
  var base_1 = require("./base");
20
20
  Object.defineProperty(exports, "FlpSandbox", { enumerable: true, get: function () { return base_1.FlpSandbox; } });
@@ -22,4 +22,6 @@ Object.defineProperty(exports, "initAdp", { enumerable: true, get: function () {
22
22
  Object.defineProperty(exports, "generatePreviewFiles", { enumerable: true, get: function () { return base_1.generatePreviewFiles; } });
23
23
  Object.defineProperty(exports, "getPreviewPaths", { enumerable: true, get: function () { return base_1.getPreviewPaths; } });
24
24
  Object.defineProperty(exports, "sanitizeRtaConfig", { enumerable: true, get: function () { return base_1.sanitizeRtaConfig; } });
25
+ Object.defineProperty(exports, "logRemoteUrl", { enumerable: true, get: function () { return base_1.logRemoteUrl; } });
26
+ Object.defineProperty(exports, "isRemoteConnectionsEnabled", { enumerable: true, get: function () { return base_1.isRemoteConnectionsEnabled; } });
25
27
  //# sourceMappingURL=index.js.map
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const logger_1 = require("@sap-ux/logger");
4
4
  const flp_1 = require("../base/flp");
5
5
  const config_1 = require("../base/config");
6
+ const remote_url_1 = require("../base/remote-url");
6
7
  /**
7
8
  * Create the router that is to be exposed as UI5 middleware.
8
9
  *
@@ -33,6 +34,9 @@ async function createRouter({ resources, options, middlewareUtil }, logger) {
33
34
  throw new Error('No manifest.json found.');
34
35
  }
35
36
  }
37
+ if ((0, remote_url_1.isRemoteConnectionsEnabled)()) {
38
+ await (0, remote_url_1.logRemoteUrl)(logger);
39
+ }
36
40
  // add exposed endpoints for cds-plugin-ui5
37
41
  flp.router.getAppPages = () => (0, config_1.getPreviewPaths)(config).map(({ path }) => path);
38
42
  return flp.router;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
11
11
  },
12
- "version": "0.23.9",
12
+ "version": "0.23.11",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -25,18 +25,21 @@
25
25
  "ejs": "3.1.10",
26
26
  "mem-fs": "2.1.0",
27
27
  "mem-fs-editor": "9.4.0",
28
- "@sap-ux/adp-tooling": "0.15.27",
28
+ "qrcode": "1.5.4",
29
+ "@sap/bas-sdk": "3.11.6",
30
+ "@sap-ux/adp-tooling": "0.15.28",
29
31
  "@sap-ux/btp-utils": "1.1.1",
30
32
  "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.0",
31
33
  "@sap-ux/feature-toggle": "0.3.1",
32
34
  "@sap-ux/logger": "0.7.0",
33
- "@sap-ux/project-access": "1.30.14",
35
+ "@sap-ux/project-access": "1.31.0",
34
36
  "@sap-ux/system-access": "0.6.16",
35
37
  "@sap-ux/i18n": "0.3.3"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@sap-ux-private/playwright": "0.2.0",
39
41
  "@types/connect": "^3.4.38",
42
+ "@types/qrcode": "1.5.5",
40
43
  "@types/ejs": "3.1.2",
41
44
  "@types/express": "4.17.21",
42
45
  "@types/mem-fs": "1.1.2",