@sap-ux/preview-middleware 0.25.37 → 0.25.38

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.
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+
3
+ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/info-center-message"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_info_center_message) {
4
+ "use strict";
5
+
6
+ const MessageBarType = ___sap_ux_private_control_property_editor_common["MessageBarType"];
7
+ const sendInfoCenterMessage = ___utils_info_center_message["sendInfoCenterMessage"];
8
+ const CHANGE_TYPE = {
9
+ addXML: 'addXML',
10
+ codeExt: 'codeExt'
11
+ };
12
+ /**
13
+ * Type guard for changes that reference a fragment or controller extension file.
14
+ *
15
+ * @param change flex change object
16
+ * @returns true if the change is an addXML with fragmentPath or a codeExt with codeRef
17
+ */
18
+ function isFragmentOrCodeExtChange(change) {
19
+ return !!change.reference && (change.changeType === CHANGE_TYPE.addXML && !!change.content?.fragmentPath || change.changeType === CHANGE_TYPE.codeExt && !!change.content?.codeRef);
20
+ }
21
+
22
+ /**
23
+ * Builds a lookup map from module name patterns to change metadata
24
+ * for addXML and codeExt changes.
25
+ *
26
+ * @param changes record of change objects keyed by flex key
27
+ * @returns map from module name substring to orphaned change entry
28
+ */
29
+ function buildModuleNameMap(changes) {
30
+ const map = new Map();
31
+ for (const change of Object.values(changes)) {
32
+ if (!isFragmentOrCodeExtChange(change)) {
33
+ continue;
34
+ }
35
+ const prefix = change.reference.replaceAll('.', '/');
36
+ const path = change.changeType === CHANGE_TYPE.addXML ? change.content?.fragmentPath ?? '' : change.content?.codeRef ?? '';
37
+ const changeFileName = `${change.fileName}.${change.fileType ?? 'change'}`;
38
+ const key = change.moduleName ?? `${prefix}/changes/${path}`;
39
+ map.set(key, {
40
+ changeFileName,
41
+ filePath: path,
42
+ changeType: change.changeType
43
+ });
44
+ }
45
+ return map;
46
+ }
47
+
48
+ /**
49
+ * Creates an error handler that matches error messages against known module names
50
+ * and sends InfoCenter errors for orphaned change files.
51
+ *
52
+ * @param moduleNameMap map from module name substring to orphaned change entry
53
+ * @returns error handler function
54
+ */
55
+ function createErrorHandler(moduleNameMap, restoreConsole) {
56
+ return message => {
57
+ for (const [moduleName, entry] of moduleNameMap) {
58
+ if (message.includes(moduleName)) {
59
+ sendInfoCenterMessage({
60
+ title: {
61
+ key: 'ADP_ORPHANED_CHANGE_ERROR_TITLE'
62
+ },
63
+ description: {
64
+ key: 'ADP_ORPHANED_FILE_DESCRIPTION',
65
+ params: [entry.filePath, entry.changeFileName]
66
+ },
67
+ type: MessageBarType.error
68
+ }).catch(error => {
69
+ log.error('Failed to send orphaned change InfoCenter message', error);
70
+ });
71
+ moduleNameMap.delete(moduleName);
72
+ if (moduleNameMap.size === 0) {
73
+ restoreConsole();
74
+ }
75
+ break;
76
+ }
77
+ }
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Initializes orphaned change file detection.
83
+ *
84
+ * Fetches loaded flex changes, builds a lookup map of module names for addXML and codeExt changes,
85
+ * and wraps console.error to intercept UI5 flex change application errors. When UI5 fails to load
86
+ * a fragment or controller extension referenced by a change file, the error is intercepted and an
87
+ * actionable message is shown in the InfoCenter advising the user to delete the orphaned change file.
88
+ */
89
+ async function initOrphanedChangeDetection() {
90
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
91
+ const response = await fetch(`${baseUrl}/preview/api/changes`, {
92
+ method: 'GET',
93
+ headers: {
94
+ 'content-type': 'application/json'
95
+ }
96
+ });
97
+ if (!response.ok) {
98
+ log.error(`Failed to fetch changes for orphaned change detection: ${response.status}`);
99
+ return;
100
+ }
101
+ const changes = await response.json();
102
+ const moduleNameMap = buildModuleNameMap(changes);
103
+ if (moduleNameMap.size === 0) {
104
+ return;
105
+ }
106
+ const consoleRef = globalThis.console;
107
+ const originalConsoleError = consoleRef.error;
108
+ const restore = () => {
109
+ consoleRef.error = originalConsoleError;
110
+ };
111
+ const handler = createErrorHandler(moduleNameMap, restore);
112
+ const safetyTimeout = setTimeout(restore, 60_000);
113
+ consoleRef.error = (...args) => {
114
+ originalConsoleError.apply(consoleRef, args);
115
+ const message = args.filter(arg => typeof arg === 'string').join('');
116
+ handler(message);
117
+ if (moduleNameMap.size === 0) {
118
+ clearTimeout(safetyTimeout);
119
+ }
120
+ };
121
+ }
122
+ var __exports = {
123
+ __esModule: true
124
+ };
125
+ __exports.initOrphanedChangeDetection = initOrphanedChangeDetection;
126
+ return __exports;
127
+ });
128
+ //# sourceMappingURL=change-file-validator.js.map
@@ -0,0 +1,158 @@
1
+ import log from 'sap/base/Log';
2
+ import { MessageBarType } from '@sap-ux-private/control-property-editor-common';
3
+
4
+ import { sendInfoCenterMessage } from '../utils/info-center-message';
5
+
6
+ const CHANGE_TYPE = {
7
+ addXML: 'addXML',
8
+ codeExt: 'codeExt'
9
+ };
10
+
11
+ type FlexChangeType = (typeof CHANGE_TYPE)[keyof typeof CHANGE_TYPE];
12
+
13
+ interface ChangeContent {
14
+ fragmentPath?: string;
15
+ codeRef?: string;
16
+ }
17
+
18
+ interface Change {
19
+ changeType: string;
20
+ fileName: string;
21
+ fileType?: string;
22
+ reference: string;
23
+ moduleName?: string;
24
+ content?: ChangeContent;
25
+ }
26
+
27
+ interface OrphanedChangeEntry {
28
+ changeFileName: string;
29
+ filePath: string;
30
+ changeType: FlexChangeType;
31
+ }
32
+
33
+ interface RelevantChange extends Change {
34
+ changeType: FlexChangeType;
35
+ reference: string;
36
+ }
37
+
38
+ /**
39
+ * Type guard for changes that reference a fragment or controller extension file.
40
+ *
41
+ * @param change flex change object
42
+ * @returns true if the change is an addXML with fragmentPath or a codeExt with codeRef
43
+ */
44
+ function isFragmentOrCodeExtChange(change: Change): change is RelevantChange {
45
+ return (
46
+ !!change.reference &&
47
+ ((change.changeType === CHANGE_TYPE.addXML && !!change.content?.fragmentPath) ||
48
+ (change.changeType === CHANGE_TYPE.codeExt && !!change.content?.codeRef))
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Builds a lookup map from module name patterns to change metadata
54
+ * for addXML and codeExt changes.
55
+ *
56
+ * @param changes record of change objects keyed by flex key
57
+ * @returns map from module name substring to orphaned change entry
58
+ */
59
+ function buildModuleNameMap(changes: Record<string, Change>): Map<string, OrphanedChangeEntry> {
60
+ const map = new Map<string, OrphanedChangeEntry>();
61
+
62
+ for (const change of Object.values(changes)) {
63
+ if (!isFragmentOrCodeExtChange(change)) {
64
+ continue;
65
+ }
66
+
67
+ const prefix = change.reference.replaceAll('.', '/');
68
+ const path = change.changeType === CHANGE_TYPE.addXML ? change.content?.fragmentPath ?? '' : change.content?.codeRef ?? '';
69
+ const changeFileName = `${change.fileName}.${change.fileType ?? 'change'}`;
70
+ const key = change.moduleName ?? `${prefix}/changes/${path}`;
71
+
72
+ map.set(key, { changeFileName, filePath: path, changeType: change.changeType });
73
+ }
74
+
75
+ return map;
76
+ }
77
+
78
+ /**
79
+ * Creates an error handler that matches error messages against known module names
80
+ * and sends InfoCenter errors for orphaned change files.
81
+ *
82
+ * @param moduleNameMap map from module name substring to orphaned change entry
83
+ * @returns error handler function
84
+ */
85
+ function createErrorHandler(
86
+ moduleNameMap: Map<string, OrphanedChangeEntry>,
87
+ restoreConsole: () => void
88
+ ): (message: string) => void {
89
+ return (message: string) => {
90
+ for (const [moduleName, entry] of moduleNameMap) {
91
+ if (message.includes(moduleName)) {
92
+ sendInfoCenterMessage({
93
+ title: { key: 'ADP_ORPHANED_CHANGE_ERROR_TITLE' },
94
+ description: {
95
+ key: 'ADP_ORPHANED_FILE_DESCRIPTION',
96
+ params: [entry.filePath, entry.changeFileName]
97
+ },
98
+ type: MessageBarType.error
99
+ }).catch((error) => {
100
+ log.error('Failed to send orphaned change InfoCenter message', error);
101
+ });
102
+ moduleNameMap.delete(moduleName);
103
+ if (moduleNameMap.size === 0) {
104
+ restoreConsole();
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Initializes orphaned change file detection.
114
+ *
115
+ * Fetches loaded flex changes, builds a lookup map of module names for addXML and codeExt changes,
116
+ * and wraps console.error to intercept UI5 flex change application errors. When UI5 fails to load
117
+ * a fragment or controller extension referenced by a change file, the error is intercepted and an
118
+ * actionable message is shown in the InfoCenter advising the user to delete the orphaned change file.
119
+ */
120
+ export async function initOrphanedChangeDetection(): Promise<void> {
121
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
122
+ const response = await fetch(`${baseUrl}/preview/api/changes`, {
123
+ method: 'GET',
124
+ headers: { 'content-type': 'application/json' }
125
+ });
126
+
127
+ if (!response.ok) {
128
+ log.error(`Failed to fetch changes for orphaned change detection: ${response.status}`);
129
+ return;
130
+ }
131
+
132
+ const changes = (await response.json()) as Record<string, Change>;
133
+ const moduleNameMap = buildModuleNameMap(changes);
134
+
135
+ if (moduleNameMap.size === 0) {
136
+ return;
137
+ }
138
+
139
+ const consoleRef = globalThis.console;
140
+ const originalConsoleError = consoleRef.error;
141
+
142
+ const restore = (): void => {
143
+ consoleRef.error = originalConsoleError;
144
+ };
145
+
146
+ const handler = createErrorHandler(moduleNameMap, restore);
147
+
148
+ const safetyTimeout = setTimeout(restore, 60_000);
149
+
150
+ consoleRef.error = (...args: unknown[]) => {
151
+ originalConsoleError.apply(consoleRef, args);
152
+ const message = args.filter((arg): arg is string => typeof arg === 'string').join('');
153
+ handler(message);
154
+ if (moduleNameMap.size === 0) {
155
+ clearTimeout(safetyTimeout);
156
+ }
157
+ };
158
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/version", "../cpe/init", "./sync-views-utils", "../utils/application", "./quick-actions/load", "./init-dialogs", "../utils/info-center-message", "../cpe/communication-service"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_version, __init, ___sync_views_utils, ___utils_application, ___quick_actions_load, ___init_dialogs, ___utils_info_center_message, ___cpe_communication_service) {
3
+ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/version", "../cpe/init", "./sync-views-utils", "../utils/application", "./quick-actions/load", "./init-dialogs", "../utils/info-center-message", "../cpe/communication-service", "./change-file-validator"], function (log, ___sap_ux_private_control_property_editor_common, ___utils_version, __init, ___sync_views_utils, ___utils_application, ___quick_actions_load, ___init_dialogs, ___utils_info_center_message, ___cpe_communication_service, ___change_file_validator) {
4
4
  "use strict";
5
5
 
6
6
  function _interopRequireDefault(obj) {
@@ -38,6 +38,7 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
38
38
  const initDialogs = ___init_dialogs["initDialogs"];
39
39
  const sendInfoCenterMessage = ___utils_info_center_message["sendInfoCenterMessage"];
40
40
  const CommunicationService = ___cpe_communication_service["CommunicationService"];
41
+ const initOrphanedChangeDetection = ___change_file_validator["initOrphanedChangeDetection"];
41
42
  var __exports = async function (rta) {
42
43
  const flexSettings = rta.getFlexSettings();
43
44
  if (flexSettings.telemetry === true) {
@@ -104,6 +105,9 @@ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-privat
104
105
  CommunicationService.sendAction(toggleAppPreviewVisibility(false));
105
106
  return;
106
107
  }
108
+ initOrphanedChangeDetection().catch(error => {
109
+ log.error('Failed to run orphaned change detection', error);
110
+ });
107
111
  log.debug('ADP init executed.');
108
112
  };
109
113
  return __exports;
@@ -23,6 +23,7 @@ import { loadDefinitions } from './quick-actions/load';
23
23
  import { initDialogs } from './init-dialogs';
24
24
  import { sendInfoCenterMessage } from '../utils/info-center-message';
25
25
  import { CommunicationService } from '../cpe/communication-service';
26
+ import { initOrphanedChangeDetection } from './change-file-validator';
26
27
 
27
28
  export default async function (rta: RuntimeAuthoring) {
28
29
  const flexSettings = rta.getFlexSettings();
@@ -88,5 +89,9 @@ export default async function (rta: RuntimeAuthoring) {
88
89
  return;
89
90
  }
90
91
 
92
+ initOrphanedChangeDetection().catch((error) => {
93
+ log.error('Failed to run orphaned change detection', error);
94
+ });
95
+
91
96
  log.debug('ADP init executed.');
92
97
  }
@@ -67,6 +67,8 @@ ADP_CREATE_CONTROLLER_EXTENSION_TITLE = Create Controller Extension
67
67
  ADP_CREATE_CONTROLLER_EXTENSION_DESCRIPTION = Controller extension with name ''{0}'' was created.
68
68
  ADP_ODATA_HEALTH_CHECK_TITLE = OData Service Health Check
69
69
  ADP_ODATA_SERVICE_DOWN_DESCRIPTION = The OData service with the {0} endpoint is down. Error: {1}.
70
+ ADP_ORPHANED_CHANGE_ERROR_TITLE = Missing File Detected
71
+ ADP_ORPHANED_FILE_DESCRIPTION = The "{0}" file referenced by the "{1}" change was not found. To resolve this error, delete the change file.
70
72
  ADP_ADD_ACTION_DIALOG_ACTION_ID_LABEL = Action ID
71
73
  ADP_ADD_ACTION_DIALOG_BUTTON_TEXT_LABEL = Button Text
72
74
  ADP_ADD_ACTION_DIALOG_HANDLER_METHOD_LABEL = Handler Method
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.25.37",
12
+ "version": "0.25.38",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -27,13 +27,13 @@
27
27
  "mem-fs-editor": "9.4.0",
28
28
  "qrcode": "1.5.4",
29
29
  "@sap/bas-sdk": "3.13.6",
30
+ "@sap-ux/btp-utils": "1.1.14",
30
31
  "@sap-ux/adp-tooling": "0.18.128",
32
+ "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.24",
31
33
  "@sap-ux/feature-toggle": "0.3.8",
32
- "@sap-ux/btp-utils": "1.1.14",
33
34
  "@sap-ux/logger": "0.8.5",
34
- "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.24",
35
- "@sap-ux/project-access": "1.36.2",
36
35
  "@sap-ux/system-access": "0.7.10",
36
+ "@sap-ux/project-access": "1.36.2",
37
37
  "@sap-ux/i18n": "0.3.11"
38
38
  },
39
39
  "devDependencies": {
@@ -53,10 +53,10 @@
53
53
  "nock": "14.0.11",
54
54
  "npm-run-all2": "8.0.4",
55
55
  "supertest": "7.2.2",
56
- "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.25.37",
57
- "@sap-ux/axios-extension": "1.25.34",
56
+ "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.25.38",
57
+ "@sap-ux/store": "1.5.13",
58
58
  "@sap-ux/ui5-info": "0.13.20",
59
- "@sap-ux/store": "1.5.13"
59
+ "@sap-ux/axios-extension": "1.25.34"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "express": "4"