@sap-ux/adp-tooling 0.12.104 → 0.12.105

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.
@@ -2,7 +2,7 @@ import type { ToolsLogger } from '@sap-ux/logger';
2
2
  import type { Manifest, ManifestNamespace } from '@sap-ux/project-access';
3
3
  import { type AbapServiceProvider } from '@sap-ux/axios-extension';
4
4
  import type { DescriptorVariant } from '../../types';
5
- type DataSources = Record<string, ManifestNamespace.DataSource>;
5
+ export type DataSources = Record<string, ManifestNamespace.DataSource>;
6
6
  /**
7
7
  * Retrieves the inbound navigation configurations from the project's manifest.
8
8
  *
@@ -99,5 +99,4 @@ export declare class ManifestService {
99
99
  */
100
100
  getDataSourceMetadata(dataSourceId: string): Promise<string>;
101
101
  }
102
- export {};
103
102
  //# sourceMappingURL=manifest-service.d.ts.map
@@ -18,7 +18,7 @@ interface InboundChange extends ManifestChangeProperties {
18
18
  * @param {Editor} fs - The `mem-fs-editor` instance used for file operations.
19
19
  * @returns {void}
20
20
  */
21
- export declare function writeAnnotationChange(projectPath: string, timestamp: number, annotation: AnnotationsData['annotation'], change: ManifestChangeProperties, fs: Editor): void;
21
+ export declare function writeAnnotationChange(projectPath: string, timestamp: number, annotation: AnnotationsData['annotation'], change: ManifestChangeProperties | undefined, fs: Editor): void;
22
22
  /**
23
23
  * Writes a given change object to a file within a specified folder in the project's 'changes' directory.
24
24
  * If an additional subdirectory is specified, the change file is written there.
@@ -27,11 +27,14 @@ const ejs_1 = require("ejs");
27
27
  */
28
28
  function writeAnnotationChange(projectPath, timestamp, annotation, change, fs) {
29
29
  try {
30
- const changeFileName = `id_${timestamp}_addAnnotationsToOData.change`;
31
30
  const changesFolderPath = path_1.default.join(projectPath, project_access_1.DirName.Webapp, project_access_1.DirName.Changes);
32
- const changeFilePath = path_1.default.join(changesFolderPath, changeFileName);
33
31
  const annotationsFolderPath = path_1.default.join(changesFolderPath, project_access_1.DirName.Annotations);
34
- writeChangeToFile(changeFilePath, change, fs);
32
+ if (change) {
33
+ const changeFileName = `id_${timestamp}_addAnnotationsToOData.change`;
34
+ const changeFilePath = path_1.default.join(changesFolderPath, changeFileName);
35
+ change.fileName = `${change.fileName}_addAnnotationsToOData`;
36
+ writeChangeToFile(changeFilePath, change, fs);
37
+ }
35
38
  if (!annotation.filePath) {
36
39
  const annotationsTemplate = path_1.default.join(__dirname, '..', '..', 'templates', 'changes', "annotation.xml" /* TemplateFileName.Annotation */);
37
40
  const { namespaces, serviceUrl } = annotation;
@@ -12,7 +12,8 @@ declare global {
12
12
  export declare const enum ApiRoutes {
13
13
  FRAGMENT = "/adp/api/fragment",
14
14
  CONTROLLER = "/adp/api/controller",
15
- CODE_EXT = "/adp/api/code_ext/:controllerName"
15
+ CODE_EXT = "/adp/api/code_ext/:controllerName",
16
+ ANNOTATION = "/adp/api/annotation"
16
17
  }
17
18
  /**
18
19
  * Instance of an adaptation project handling requests and data transformation.
@@ -171,6 +171,8 @@ class AdpPreview {
171
171
  router.get("/adp/api/controller" /* ApiRoutes.CONTROLLER */, this.routesHandler.handleReadAllControllers);
172
172
  router.post("/adp/api/controller" /* ApiRoutes.CONTROLLER */, this.routesHandler.handleWriteControllerExt);
173
173
  router.get("/adp/api/code_ext/:controllerName" /* ApiRoutes.CODE_EXT */, this.routesHandler.handleGetControllerExtensionData);
174
+ router.post("/adp/api/annotation" /* ApiRoutes.ANNOTATION */, this.routesHandler.handleCreateAnnotationFile);
175
+ router.get("/adp/api/annotation" /* ApiRoutes.ANNOTATION */, this.routesHandler.handleGetAllAnnotationFilesMappedByDataSource);
174
176
  }
175
177
  /**
176
178
  * Handles different types of change requests to project files.
@@ -192,6 +194,9 @@ class AdpPreview {
192
194
  if ((0, change_handler_1.isAddXMLChange)(change)) {
193
195
  (0, change_handler_1.addXmlFragment)(this.util.getProject().getSourcePath(), change, fs, logger);
194
196
  }
197
+ if ((0, change_handler_1.isAddAnnotationChange)(change)) {
198
+ await (0, change_handler_1.addAnnotationFile)(this.util.getProject().getSourcePath(), this.util.getProject().getRootPath(), change, fs, logger);
199
+ }
195
200
  break;
196
201
  default:
197
202
  // no need to handle delete changes
@@ -1,5 +1,5 @@
1
1
  import type { Editor } from 'mem-fs-editor';
2
- import type { AddXMLChange, CommonChangeProperties } from '../types';
2
+ import type { AddXMLChange, CommonChangeProperties, AnnotationFileChange } from '../types';
3
3
  import type { Logger } from '@sap-ux/logger';
4
4
  /**
5
5
  * A mapping object that defines how to extract change content data from changes based on their type.
@@ -29,6 +29,14 @@ export declare function tryFixChange(change: CommonChangeProperties, logger: Log
29
29
  * indicating the change is of type `AddXMLChange`.
30
30
  */
31
31
  export declare function isAddXMLChange(change: CommonChangeProperties): change is AddXMLChange;
32
+ /**
33
+ * Determines whether a given change is of type `AnnotationFileChange`.
34
+ *
35
+ * @param {CommonChangeProperties} change - The change object to check.
36
+ * @returns {boolean} `true` if the `changeType` is either 'appdescr_app_addAnnotationsToOData',
37
+ * indicating the change is of type `AnnotationFileChange`.
38
+ */
39
+ export declare function isAddAnnotationChange(change: CommonChangeProperties): change is AnnotationFileChange;
32
40
  /**
33
41
  * Asynchronously adds an XML fragment to the project if it doesn't already exist.
34
42
  *
@@ -38,4 +46,14 @@ export declare function isAddXMLChange(change: CommonChangeProperties): change i
38
46
  * @param {Logger} logger - The logging instance.
39
47
  */
40
48
  export declare function addXmlFragment(basePath: string, change: AddXMLChange, fs: Editor, logger: Logger): void;
49
+ /**
50
+ * Asynchronously adds an XML fragment to the project if it doesn't already exist.
51
+ *
52
+ * @param {string} basePath - The base path of the project.
53
+ * @param {string} projectRoot - The root path of the project.
54
+ * @param {AddXMLChange} change - The change data, including the fragment path.
55
+ * @param {Editor} fs - The mem-fs-editor instance.
56
+ * @param {Logger} logger - The logging instance.
57
+ */
58
+ export declare function addAnnotationFile(basePath: string, projectRoot: string, change: AnnotationFileChange, fs: Editor, logger: Logger): Promise<void>;
41
59
  //# sourceMappingURL=change-handler.d.ts.map
@@ -3,11 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.moduleNameContentMap = void 0;
4
4
  exports.tryFixChange = tryFixChange;
5
5
  exports.isAddXMLChange = isAddXMLChange;
6
+ exports.isAddAnnotationChange = isAddAnnotationChange;
6
7
  exports.addXmlFragment = addXmlFragment;
8
+ exports.addAnnotationFile = addAnnotationFile;
7
9
  const path_1 = require("path");
8
10
  const project_access_1 = require("@sap-ux/project-access");
9
11
  const ejs_1 = require("ejs");
10
12
  const crypto_1 = require("crypto");
13
+ const manifest_service_1 = require("../base/abap/manifest-service");
14
+ const helper_1 = require("../base/helper");
15
+ const system_access_1 = require("@sap-ux/system-access");
16
+ const odata_service_writer_1 = require("@sap-ux/odata-service-writer");
17
+ const editors_1 = require("../writer/editors");
11
18
  const OBJECT_PAGE_CUSTOM_SECTION = 'OBJECT_PAGE_CUSTOM_SECTION';
12
19
  const CUSTOM_ACTION = 'CUSTOM_ACTION';
13
20
  const OBJECT_PAGE_HEADER_FIELD = 'OBJECT_PAGE_HEADER_FIELD';
@@ -156,6 +163,16 @@ function tryFixChange(change, logger) {
156
163
  function isAddXMLChange(change) {
157
164
  return change.changeType === 'addXML' || change.changeType === 'addXMLAtExtensionPoint';
158
165
  }
166
+ /**
167
+ * Determines whether a given change is of type `AnnotationFileChange`.
168
+ *
169
+ * @param {CommonChangeProperties} change - The change object to check.
170
+ * @returns {boolean} `true` if the `changeType` is either 'appdescr_app_addAnnotationsToOData',
171
+ * indicating the change is of type `AnnotationFileChange`.
172
+ */
173
+ function isAddAnnotationChange(change) {
174
+ return change.changeType === 'appdescr_app_addAnnotationsToOData';
175
+ }
159
176
  /**
160
177
  * Asynchronously adds an XML fragment to the project if it doesn't already exist.
161
178
  *
@@ -187,4 +204,52 @@ function addXmlFragment(basePath, change, fs, logger) {
187
204
  logger.error(`Failed to create XML Fragment "${fragmentPath}": ${error}`);
188
205
  }
189
206
  }
207
+ /**
208
+ * Asynchronously adds an XML fragment to the project if it doesn't already exist.
209
+ *
210
+ * @param {string} basePath - The base path of the project.
211
+ * @param {string} projectRoot - The root path of the project.
212
+ * @param {AddXMLChange} change - The change data, including the fragment path.
213
+ * @param {Editor} fs - The mem-fs-editor instance.
214
+ * @param {Logger} logger - The logging instance.
215
+ */
216
+ async function addAnnotationFile(basePath, projectRoot, change, fs, logger) {
217
+ const { dataSourceId, annotations, dataSource } = change.content;
218
+ const annotationDataSourceKey = annotations[0];
219
+ const annotationUriSegments = dataSource[annotationDataSourceKey].uri.split('/');
220
+ annotationUriSegments.shift();
221
+ const fullPath = (0, path_1.join)(basePath, project_access_1.DirName.Changes, ...annotationUriSegments);
222
+ try {
223
+ const manifestService = await getManifestService(projectRoot, logger);
224
+ const metadata = await manifestService.getDataSourceMetadata(dataSourceId);
225
+ const datasoruces = await manifestService.getManifestDataSources();
226
+ const namespaces = (0, odata_service_writer_1.getAnnotationNamespaces)({ metadata });
227
+ await (0, editors_1.generateChange)(projectRoot, "appdescr_app_addAnnotationsToOData" /* ChangeType.ADD_ANNOTATIONS_TO_ODATA */, {
228
+ annotation: {
229
+ dataSource: dataSourceId,
230
+ namespaces,
231
+ serviceUrl: datasoruces[dataSourceId].uri,
232
+ fileName: (0, path_1.basename)(dataSource[annotationDataSourceKey].uri)
233
+ },
234
+ variant: (0, helper_1.getVariant)(projectRoot),
235
+ isCommand: false
236
+ }, fs);
237
+ }
238
+ catch (error) {
239
+ logger.error(`Failed to create Local Annotation File "${fullPath}": ${error}`);
240
+ }
241
+ }
242
+ /**
243
+ * Returns manifest service.
244
+ *
245
+ * @param {string} basePath - The base path of the project.
246
+ * @param {Logger} logger - The logging instance.
247
+ * @returns Promise<ManifestService>
248
+ */
249
+ async function getManifestService(basePath, logger) {
250
+ const variant = (0, helper_1.getVariant)(basePath);
251
+ const { target, ignoreCertErrors = false } = await (0, helper_1.getAdpConfig)(basePath, (0, path_1.join)(basePath, project_access_1.FileName.Ui5Yaml));
252
+ const provider = await (0, system_access_1.createAbapServiceProvider)(target, { ignoreCertErrors }, true, logger);
253
+ return await manifest_service_1.ManifestService.initMergedManifest(provider, basePath, variant, logger);
254
+ }
190
255
  //# sourceMappingURL=change-handler.js.map
@@ -73,5 +73,35 @@ export default class RoutesHandler {
73
73
  * @param next Next Function
74
74
  */
75
75
  handleWriteControllerExt: (req: Request, res: Response, next: NextFunction) => Promise<void>;
76
+ /**
77
+ * Handler for writing an annotation file to the workspace.
78
+ *
79
+ * @param req Request
80
+ * @param res Response
81
+ * @param next Next Function
82
+ */
83
+ handleCreateAnnotationFile: (req: Request, res: Response, next: NextFunction) => Promise<void>;
84
+ /**
85
+ * Handler for mapping annotation files with datasoruce.
86
+ *
87
+ * @param _req Request
88
+ * @param res Response
89
+ * @param next Next Function
90
+ */
91
+ handleGetAllAnnotationFilesMappedByDataSource: (_req: Request, res: Response, next: NextFunction) => Promise<void>;
92
+ /**
93
+ * Add local annotation details to api response.
94
+ *
95
+ * @param dataSources DataSources
96
+ * @param dataSourceId string
97
+ * @param apiResponse AnnotationDataSourceMap
98
+ */
99
+ private fillAnnotationDataSourceMap;
100
+ /**
101
+ * Returns manifest service.
102
+ *
103
+ * @returns Promise<ManifestService>
104
+ */
105
+ private getManifestService;
76
106
  }
77
107
  //# sourceMappingURL=routes-handler.d.ts.map
@@ -33,6 +33,11 @@ const ejs_1 = require("ejs");
33
33
  const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
34
34
  const btp_utils_1 = require("@sap-ux/btp-utils");
35
35
  const project_access_1 = require("@sap-ux/project-access");
36
+ const editors_1 = require("../writer/editors");
37
+ const manifest_service_1 = require("../base/abap/manifest-service");
38
+ const helper_1 = require("../base/helper");
39
+ const odata_service_writer_1 = require("@sap-ux/odata-service-writer");
40
+ const system_access_1 = require("@sap-ux/system-access");
36
41
  /**
37
42
  * @description Handles API Routes
38
43
  */
@@ -231,6 +236,125 @@ class RoutesHandler {
231
236
  next(e);
232
237
  }
233
238
  };
239
+ /**
240
+ * Handler for writing an annotation file to the workspace.
241
+ *
242
+ * @param req Request
243
+ * @param res Response
244
+ * @param next Next Function
245
+ */
246
+ handleCreateAnnotationFile = async (req, res, next) => {
247
+ try {
248
+ const { dataSource, serviceUrl } = req.body;
249
+ if (!dataSource) {
250
+ res.status(400 /* HttpStatusCodes.BAD_REQUEST */).send('No datasource found in manifest!');
251
+ this.logger.debug('Bad request. Could not find a datasource in manifest!');
252
+ return;
253
+ }
254
+ const project = this.util.getProject();
255
+ const projectRoot = project.getRootPath();
256
+ const manifestService = await this.getManifestService();
257
+ const metadata = await manifestService.getDataSourceMetadata(dataSource);
258
+ const namespaces = (0, odata_service_writer_1.getAnnotationNamespaces)({ metadata });
259
+ const fsEditor = await (0, editors_1.generateChange)(projectRoot, "appdescr_app_addAnnotationsToOData" /* ChangeType.ADD_ANNOTATIONS_TO_ODATA */, {
260
+ annotation: {
261
+ dataSource,
262
+ namespaces,
263
+ serviceUrl: serviceUrl
264
+ },
265
+ variant: (0, helper_1.getVariant)(projectRoot),
266
+ isCommand: false
267
+ });
268
+ fsEditor.commit((err) => this.logger.error(err));
269
+ const message = 'Annotation file created!';
270
+ res.status(201 /* HttpStatusCodes.CREATED */).send(message);
271
+ }
272
+ catch (e) {
273
+ const sanitizedMsg = (0, sanitize_filename_1.default)(e.message);
274
+ this.logger.error(sanitizedMsg);
275
+ res.status(500 /* HttpStatusCodes.INTERNAL_SERVER_ERROR */).send(sanitizedMsg);
276
+ next(e);
277
+ }
278
+ };
279
+ /**
280
+ * Handler for mapping annotation files with datasoruce.
281
+ *
282
+ * @param _req Request
283
+ * @param res Response
284
+ * @param next Next Function
285
+ */
286
+ handleGetAllAnnotationFilesMappedByDataSource = async (_req, res, next) => {
287
+ try {
288
+ const isRunningInBAS = (0, btp_utils_1.isAppStudio)();
289
+ const manifestService = await this.getManifestService();
290
+ const dataSources = manifestService.getManifestDataSources();
291
+ const apiResponse = {};
292
+ for (const dataSourceId in dataSources) {
293
+ if (dataSources[dataSourceId].type === 'OData') {
294
+ apiResponse[dataSourceId] = {
295
+ annotationDetails: {
296
+ annotationExistsInWS: false
297
+ },
298
+ serviceUrl: dataSources[dataSourceId].uri,
299
+ isRunningInBAS: isRunningInBAS
300
+ };
301
+ }
302
+ this.fillAnnotationDataSourceMap(dataSources, dataSourceId, apiResponse);
303
+ }
304
+ this.sendFilesResponse(res, apiResponse);
305
+ }
306
+ catch (e) {
307
+ this.handleErrorMessage(res, next, e);
308
+ }
309
+ };
310
+ /**
311
+ * Add local annotation details to api response.
312
+ *
313
+ * @param dataSources DataSources
314
+ * @param dataSourceId string
315
+ * @param apiResponse AnnotationDataSourceMap
316
+ */
317
+ fillAnnotationDataSourceMap(dataSources, dataSourceId, apiResponse) {
318
+ const project = this.util.getProject();
319
+ const getPath = (projectPath, relativePath) => path.join(projectPath, project_access_1.DirName.Changes, relativePath).split(path.sep).join(path.posix.sep);
320
+ const annotations = dataSources[dataSourceId].settings?.annotations
321
+ ? [...dataSources[dataSourceId].settings.annotations].reverse()
322
+ : [];
323
+ for (const annotation of annotations) {
324
+ const annotationSetting = dataSources[annotation];
325
+ if (annotationSetting.type === 'ODataAnnotation') {
326
+ const ui5NamespaceUri = `ui5://${project.getNamespace()}`;
327
+ if (annotationSetting.uri.startsWith(ui5NamespaceUri)) {
328
+ const localAnnotationUri = annotationSetting.uri.replace(ui5NamespaceUri, '');
329
+ const annotationPath = getPath(project.getSourcePath(), localAnnotationUri);
330
+ const annotationPathFromRoot = getPath(project.getName(), localAnnotationUri);
331
+ const annotationExists = fs.existsSync(annotationPath);
332
+ apiResponse[dataSourceId].annotationDetails = {
333
+ fileName: path.parse(localAnnotationUri).base,
334
+ annotationPath: os.platform() === 'win32' ? `/${annotationPath}` : annotationPath,
335
+ annotationPathFromRoot,
336
+ annotationExistsInWS: annotationExists
337
+ };
338
+ }
339
+ if (apiResponse[dataSourceId].annotationDetails.annotationExistsInWS) {
340
+ break;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ /**
346
+ * Returns manifest service.
347
+ *
348
+ * @returns Promise<ManifestService>
349
+ */
350
+ async getManifestService() {
351
+ const project = this.util.getProject();
352
+ const basePath = project.getRootPath();
353
+ const variant = (0, helper_1.getVariant)(basePath);
354
+ const { target, ignoreCertErrors = false } = await (0, helper_1.getAdpConfig)(basePath, path.join(basePath, project_access_1.FileName.Ui5Yaml));
355
+ const provider = await (0, system_access_1.createAbapServiceProvider)(target, { ignoreCertErrors }, true, this.logger);
356
+ return await manifest_service_1.ManifestService.initMergedManifest(provider, basePath, variant, this.logger);
357
+ }
234
358
  }
235
359
  exports.default = RoutesHandler;
236
360
  //# sourceMappingURL=routes-handler.js.map
package/dist/types.d.ts CHANGED
@@ -181,6 +181,21 @@ export interface CodeExtChange extends CommonChangeProperties {
181
181
  controllerName: string;
182
182
  };
183
183
  }
184
+ export interface AnnotationFileChange extends CommonChangeProperties {
185
+ changeType: 'appdescr_app_addAnnotationsToOData';
186
+ creation: string;
187
+ content: {
188
+ dataSourceId: string;
189
+ annotations: string[];
190
+ annotationsInsertPosition: 'END';
191
+ dataSource: {
192
+ [fileName: string]: {
193
+ uri: string;
194
+ type: 'ODataAnnotation';
195
+ };
196
+ };
197
+ };
198
+ }
184
199
  export interface ParamCheck {
185
200
  shouldApply: boolean;
186
201
  value: string | undefined;
@@ -311,6 +326,8 @@ export declare const enum ChangeType {
311
326
  export type GeneratorData<T extends ChangeType> = T extends ChangeType.ADD_ANNOTATIONS_TO_ODATA ? AnnotationsData : T extends ChangeType.ADD_COMPONENT_USAGES ? ComponentUsagesData : T extends ChangeType.ADD_LIBRARY_REFERENCE ? ComponentUsagesData : T extends ChangeType.ADD_NEW_MODEL ? NewModelData : T extends ChangeType.CHANGE_DATA_SOURCE ? DataSourceData : T extends ChangeType.CHANGE_INBOUND ? InboundData : never;
312
327
  export interface AnnotationsData {
313
328
  variant: DescriptorVariant;
329
+ /** Flag for differentiating the annotation creation call from CLI and from CPE */
330
+ isCommand: boolean;
314
331
  annotation: {
315
332
  /** Optional name of the annotation file. */
316
333
  fileName?: string;
@@ -89,7 +89,11 @@ class AnnotationsWriter {
89
89
  }
90
90
  const content = this.constructContent(data);
91
91
  const timestamp = Date.now();
92
- const change = (0, change_utils_1.getChange)(variant, timestamp, content, "appdescr_app_addAnnotationsToOData" /* ChangeType.ADD_ANNOTATIONS_TO_ODATA */);
92
+ let change;
93
+ // When created via command change need to be created else change is created via RTA.
94
+ if (data.isCommand) {
95
+ change = (0, change_utils_1.getChange)(variant, timestamp, content, "appdescr_app_addAnnotationsToOData" /* ChangeType.ADD_ANNOTATIONS_TO_ODATA */);
96
+ }
93
97
  (0, change_utils_1.writeAnnotationChange)(this.projectPath, timestamp, data.annotation, change, this.fs);
94
98
  }
95
99
  }
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%3Aadp-tooling"
11
11
  },
12
- "version": "0.12.104",
12
+ "version": "0.12.105",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -40,6 +40,7 @@
40
40
  "@sap-ux/project-input-validator": "0.3.4",
41
41
  "@sap-ux/system-access": "0.5.25",
42
42
  "@sap-ux/ui5-config": "0.26.0",
43
+ "@sap-ux/odata-service-writer": "0.25.1",
43
44
  "@sap-ux/i18n": "0.2.0"
44
45
  },
45
46
  "devDependencies": {