@sap-ux/adp-tooling 0.18.116 → 0.18.118

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/dist/btp/api.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ToolsLogger } from '@sap-ux/logger';
2
- import type { Uaa, BtpDestinationConfig } from '../types';
2
+ import type { Destinations } from '@sap-ux/btp-utils';
3
+ import type { Uaa, BtpDestinationConfig, CfDestinationServiceCredentials } from '../types';
3
4
  /**
4
5
  * Obtain an OAuth2 access token using the client credentials grant.
5
6
  *
@@ -19,4 +20,11 @@ export declare function getToken(uaa: Uaa, logger?: ToolsLogger): Promise<string
19
20
  * @returns The destinationConfiguration object (e.g. Name, ProxyType, URL, Authentication) or undefined on failure.
20
21
  */
21
22
  export declare function getBtpDestinationConfig(uri: string, token: string, destinationName: string, logger?: ToolsLogger): Promise<BtpDestinationConfig | undefined>;
23
+ /**
24
+ * Lists all subaccount destinations from the BTP Destination Configuration API.
25
+ *
26
+ * @param {CfDestinationServiceCredentials} credentials - Destination service credentials.
27
+ * @returns {Promise<Destinations>} Map of destination name to Destination object.
28
+ */
29
+ export declare function listBtpDestinations(credentials: CfDestinationServiceCredentials): Promise<Destinations>;
22
30
  //# sourceMappingURL=api.d.ts.map
package/dist/btp/api.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getToken = getToken;
7
7
  exports.getBtpDestinationConfig = getBtpDestinationConfig;
8
+ exports.listBtpDestinations = listBtpDestinations;
8
9
  const axios_1 = __importDefault(require("axios"));
9
10
  const i18n_1 = require("../i18n");
10
11
  /**
@@ -30,8 +31,8 @@ async function getToken(uaa, logger) {
30
31
  return response.data['access_token'];
31
32
  }
32
33
  catch (e) {
33
- logger?.error(`Failed to obtain OAuth token from ${uri}: ${e.message}`);
34
- throw new Error((0, i18n_1.t)('error.failedToGetAuthKey', { error: e.message }));
34
+ logger?.error(`Failed to obtain OAuth token from ${uri}: ${e instanceof Error ? e.message : String(e)}`);
35
+ throw new Error((0, i18n_1.t)('error.failedToGetAuthKey', { error: e instanceof Error ? e.message : String(e) }));
35
36
  }
36
37
  }
37
38
  /**
@@ -56,8 +57,41 @@ async function getBtpDestinationConfig(uri, token, destinationName, logger) {
56
57
  return config;
57
58
  }
58
59
  catch (e) {
59
- logger?.error(`Failed to fetch destination config for "${destinationName}": ${e.message}`);
60
+ logger?.error(`Failed to fetch destination config for "${destinationName}": ${e instanceof Error ? e.message : String(e)}`);
60
61
  return undefined;
61
62
  }
62
63
  }
64
+ /**
65
+ * Lists all subaccount destinations from the BTP Destination Configuration API.
66
+ *
67
+ * @param {CfDestinationServiceCredentials} credentials - Destination service credentials.
68
+ * @returns {Promise<Destinations>} Map of destination name to Destination object.
69
+ */
70
+ async function listBtpDestinations(credentials) {
71
+ const uaa = 'uaa' in credentials
72
+ ? credentials.uaa
73
+ : { clientid: credentials.clientid, clientsecret: credentials.clientsecret, url: credentials.url };
74
+ const token = await getToken(uaa);
75
+ const url = `${credentials.uri}/destination-configuration/v1/subaccountDestinations`;
76
+ try {
77
+ const response = await axios_1.default.get(url, {
78
+ headers: { Authorization: `Bearer ${token}` }
79
+ });
80
+ const configs = Array.isArray(response.data) ? response.data : [];
81
+ return configs.reduce((acc, config) => {
82
+ acc[config.Name] = {
83
+ Name: config.Name,
84
+ Host: config.URL,
85
+ Type: config.Type,
86
+ Authentication: config.Authentication,
87
+ ProxyType: config.ProxyType,
88
+ Description: config.Description ?? ''
89
+ };
90
+ return acc;
91
+ }, {});
92
+ }
93
+ catch (e) {
94
+ throw new Error((0, i18n_1.t)('error.failedToListBtpDestinations', { error: e instanceof Error ? e.message : String(e) }));
95
+ }
96
+ }
63
97
  //# sourceMappingURL=api.js.map
@@ -18,6 +18,17 @@ interface AdjustMtaYamlParams {
18
18
  * @returns {boolean} True if the selected path is a MTA project, false otherwise.
19
19
  */
20
20
  export declare function isMtaProject(selectedPath: string): boolean;
21
+ /**
22
+ * Adds a connectivity service resource to the project's mta.yaml if not already present,
23
+ * creates the CF service instance and generates a service key for it.
24
+ * Only applies to MTA projects. Required when the selected CF destination is OnPremise
25
+ * so the AppRouter can proxy requests through the Cloud Connector.
26
+ *
27
+ * @param {string} projectPath - The root path of the project.
28
+ * @param {Editor} memFs - The mem-fs editor instance.
29
+ * @param {ToolsLogger} [logger] - Optional logger.
30
+ */
31
+ export declare function addConnectivityServiceToMta(projectPath: string, memFs: Editor, logger?: ToolsLogger): Promise<void>;
21
32
  /**
22
33
  * Gets the SAP Cloud Service.
23
34
  *
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.isMtaProject = isMtaProject;
40
+ exports.addConnectivityServiceToMta = addConnectivityServiceToMta;
40
41
  exports.getSAPCloudService = getSAPCloudService;
41
42
  exports.getRouterType = getRouterType;
42
43
  exports.getAppParamsFromUI5Yaml = getAppParamsFromUI5Yaml;
@@ -60,6 +61,44 @@ const SAP_APPLICATION_CONTENT = 'com.sap.application.content';
60
61
  function isMtaProject(selectedPath) {
61
62
  return node_fs_1.default.existsSync(path.join(selectedPath, 'mta.yaml'));
62
63
  }
64
+ /**
65
+ * Adds a connectivity service resource to the project's mta.yaml if not already present,
66
+ * creates the CF service instance and generates a service key for it.
67
+ * Only applies to MTA projects. Required when the selected CF destination is OnPremise
68
+ * so the AppRouter can proxy requests through the Cloud Connector.
69
+ *
70
+ * @param {string} projectPath - The root path of the project.
71
+ * @param {Editor} memFs - The mem-fs editor instance.
72
+ * @param {ToolsLogger} [logger] - Optional logger.
73
+ */
74
+ async function addConnectivityServiceToMta(projectPath, memFs, logger) {
75
+ if (!isMtaProject(projectPath)) {
76
+ return;
77
+ }
78
+ const mtaYamlPath = path.join(projectPath, 'mta.yaml');
79
+ const yamlContent = (0, yaml_loader_1.getYamlContent)(mtaYamlPath);
80
+ if (!yamlContent) {
81
+ return;
82
+ }
83
+ const projectName = yamlContent.ID.toLowerCase();
84
+ const connectivityResourceName = `${projectName}-connectivity`;
85
+ if (yamlContent.resources?.some((r) => r.name === connectivityResourceName)) {
86
+ return;
87
+ }
88
+ await (0, api_1.createServiceInstance)('lite', connectivityResourceName, 'connectivity', { logger });
89
+ await (0, api_1.getOrCreateServiceInstanceKeys)({ names: [connectivityResourceName] }, logger);
90
+ yamlContent.resources = yamlContent.resources ?? [];
91
+ yamlContent.resources.push({
92
+ name: connectivityResourceName,
93
+ type: CF_MANAGED_SERVICE,
94
+ parameters: {
95
+ service: 'connectivity',
96
+ 'service-plan': 'lite',
97
+ 'service-name': connectivityResourceName
98
+ }
99
+ });
100
+ memFs.write(mtaYamlPath, js_yaml_1.default.dump(yamlContent, { lineWidth: -1 }));
101
+ }
63
102
  /**
64
103
  * Gets the SAP Cloud Service.
65
104
  *
@@ -0,0 +1,11 @@
1
+ import type { Destinations } from '@sap-ux/btp-utils';
2
+ /**
3
+ * Returns the list of available BTP destinations from the logged-in CF subaccount.
4
+ * Reads the destination service credentials from the CF project's service keys
5
+ * and calls the BTP Destination Configuration API directly.
6
+ *
7
+ * @param {string} projectPath - The root path of the CF app project.
8
+ * @returns {Promise<Destinations>} Map of destination name to Destination object.
9
+ */
10
+ export declare function getBtpDestinations(projectPath: string): Promise<Destinations>;
11
+ //# sourceMappingURL=destinations.d.ts.map
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getBtpDestinations = getBtpDestinations;
37
+ const path = __importStar(require("node:path"));
38
+ const api_1 = require("./api");
39
+ const api_2 = require("../../btp/api");
40
+ const yaml_loader_1 = require("../project/yaml-loader");
41
+ const i18n_1 = require("../../i18n");
42
+ /**
43
+ * Finds the name of the destination service instance declared in the MTA project's mta.yaml.
44
+ *
45
+ * @param {string} projectPath - The root path of the app project.
46
+ * @returns {string} The CF service instance name.
47
+ * @throws {Error} When the destination service instance is not found or mta.yaml cannot be read.
48
+ */
49
+ function getDestinationServiceName(projectPath) {
50
+ try {
51
+ const yamlContent = (0, yaml_loader_1.getYamlContent)(path.join(path.dirname(projectPath), 'mta.yaml'));
52
+ const name = yamlContent?.resources?.find((r) => r.parameters?.service === 'destination')?.name;
53
+ if (!name) {
54
+ throw new Error((0, i18n_1.t)('error.destinationServiceNotFoundInMtaYaml'));
55
+ }
56
+ return name;
57
+ }
58
+ catch (e) {
59
+ throw e instanceof Error ? e : new Error((0, i18n_1.t)('error.destinationServiceNotFoundInMtaYaml'));
60
+ }
61
+ }
62
+ /**
63
+ * Returns the list of available BTP destinations from the logged-in CF subaccount.
64
+ * Reads the destination service credentials from the CF project's service keys
65
+ * and calls the BTP Destination Configuration API directly.
66
+ *
67
+ * @param {string} projectPath - The root path of the CF app project.
68
+ * @returns {Promise<Destinations>} Map of destination name to Destination object.
69
+ */
70
+ async function getBtpDestinations(projectPath) {
71
+ const destinationServiceName = getDestinationServiceName(projectPath);
72
+ const serviceInfo = await (0, api_1.getOrCreateServiceInstanceKeys)({ names: [destinationServiceName] });
73
+ if (!serviceInfo?.serviceKeys?.length) {
74
+ throw new Error((0, i18n_1.t)('error.noServiceKeysFoundForDestination', { serviceInstanceName: destinationServiceName }));
75
+ }
76
+ const credentials = serviceInfo.serviceKeys[0].credentials;
77
+ return (0, api_2.listBtpDestinations)(credentials);
78
+ }
79
+ //# sourceMappingURL=destinations.js.map
@@ -1,5 +1,6 @@
1
1
  export * from './api';
2
2
  export * from './ssh';
3
3
  export * from './cli';
4
+ export * from './destinations';
4
5
  export * from './manifest';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -17,5 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./api"), exports);
18
18
  __exportStar(require("./ssh"), exports);
19
19
  __exportStar(require("./cli"), exports);
20
+ __exportStar(require("./destinations"), exports);
20
21
  __exportStar(require("./manifest"), exports);
21
22
  //# sourceMappingURL=index.js.map
@@ -1,12 +1,32 @@
1
1
  import type { YUIQuestion } from '@sap-ux/inquirer-common';
2
2
  import type { UI5FlexLayer } from '@sap-ux/project-access';
3
- import { type NewModelAnswers } from '../../types';
3
+ import type { ToolsLogger } from '@sap-ux/logger';
4
+ import { ServiceType, type DescriptorVariant, type NewModelAnswers, type NewModelData } from '../../types';
5
+ /**
6
+ * Returns the OData version string for use in change content based on the selected service type.
7
+ * Returns undefined for HTTP service type as it has no OData version.
8
+ *
9
+ * @param {ServiceType} serviceType - The selected service type.
10
+ * @returns {string | undefined} The OData version string ('2.0' or '4.0'), or undefined for HTTP.
11
+ */
12
+ export declare function getODataVersionFromServiceType(serviceType: ServiceType): string | undefined;
4
13
  /**
5
14
  * Gets the prompts for adding the new model.
6
15
  *
7
16
  * @param {string} projectPath - The root path of the project.
8
17
  * @param {UI5FlexLayer} layer - UI5 Flex layer.
18
+ * @param {ToolsLogger} [logger] - Optional logger.
9
19
  * @returns {YUIQuestion<NewModelAnswers>[]} The questions/prompts.
10
20
  */
11
- export declare function getPrompts(projectPath: string, layer: UI5FlexLayer): Promise<YUIQuestion<NewModelAnswers>[]>;
21
+ export declare function getPrompts(projectPath: string, layer: UI5FlexLayer, logger?: ToolsLogger): Promise<YUIQuestion<NewModelAnswers>[]>;
22
+ /**
23
+ * Builds the NewModelData object from the prompts answers.
24
+ *
25
+ * @param {string} projectPath - The root path of the project.
26
+ * @param {DescriptorVariant} variant - The descriptor variant of the adaptation project.
27
+ * @param {NewModelAnswers} answers - The answers to the prompts.
28
+ * @param {ToolsLogger} [logger] - Optional logger instance.
29
+ * @returns {Promise<NewModelData>} The data required by NewModelWriter.
30
+ */
31
+ export declare function createNewModelData(projectPath: string, variant: DescriptorVariant, answers: NewModelAnswers, logger?: ToolsLogger): Promise<NewModelData>;
12
32
  //# sourceMappingURL=index.d.ts.map
@@ -1,13 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getODataVersionFromServiceType = getODataVersionFromServiceType;
3
4
  exports.getPrompts = getPrompts;
5
+ exports.createNewModelData = createNewModelData;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const btp_utils_1 = require("@sap-ux/btp-utils");
9
+ const yeoman_ui_types_1 = require("@sap-devx/yeoman-ui-types");
4
10
  const i18n_1 = require("../../i18n");
5
11
  const change_utils_1 = require("../../base/change-utils");
12
+ const destinations_1 = require("../../cf/services/destinations");
13
+ const types_1 = require("../../types");
6
14
  const cf_1 = require("../../base/cf");
15
+ const helper_1 = require("../../base/helper");
7
16
  const project_input_validator_1 = require("@sap-ux/project-input-validator");
8
- const oDataVersions = [
9
- { name: '2.0', value: '2.0' },
10
- { name: '4.0', value: '4.0' }
17
+ /**
18
+ * Reads the routes array from the xs-app.json file in the project's webapp folder.
19
+ * Returns an empty array if the file does not exist or cannot be parsed.
20
+ *
21
+ * @param {string} projectPath - The root path of the project.
22
+ * @returns {XsAppRoute[]} The existing routes.
23
+ */
24
+ function readXsAppRoutes(projectPath) {
25
+ try {
26
+ const xsAppPath = (0, node_path_1.join)(projectPath, 'webapp', 'xs-app.json');
27
+ const content = JSON.parse((0, node_fs_1.readFileSync)(xsAppPath, 'utf-8'));
28
+ return Array.isArray(content?.routes) ? content.routes : [];
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ const serviceTypeChoices = [
35
+ { name: types_1.ServiceType.ODATA_V2, value: types_1.ServiceType.ODATA_V2 },
36
+ { name: types_1.ServiceType.ODATA_V4, value: types_1.ServiceType.ODATA_V4 },
37
+ { name: types_1.ServiceType.HTTP, value: types_1.ServiceType.HTTP }
11
38
  ];
12
39
  /**
13
40
  * Exucute generic validation for input.
@@ -23,6 +50,9 @@ function validatePromptInput(value) {
23
50
  return validationResult;
24
51
  }
25
52
  }
53
+ if (!/[a-zA-Z0-9]$/.test(value)) {
54
+ return (0, i18n_1.t)('validators.errorInputMustEndWithAlphanumeric');
55
+ }
26
56
  return true;
27
57
  }
28
58
  /**
@@ -64,18 +94,17 @@ function validatePromptJSON(value) {
64
94
  * Validates the OData Service name prompt.
65
95
  *
66
96
  * @param value The value to validate.
67
- * @param answers The answers object.
68
97
  * @param isCustomerBase Whether the validation is for customer usage.
69
98
  * @param changeFiles The list of existing change files to check against.
70
99
  * @returns {boolean | string} True if no duplication is found, or an error message if validation fails.
71
100
  */
72
- function validatePromptODataName(value, answers, isCustomerBase, changeFiles) {
101
+ function validatePromptODataName(value, isCustomerBase, changeFiles) {
73
102
  let validationResult = validatePromptInput(value);
74
103
  if (typeof validationResult === 'string') {
75
104
  return validationResult;
76
105
  }
77
106
  if (isCustomerBase) {
78
- validationResult = validateCustomerValue(value, 'prompts.oDataServiceNameLabel');
107
+ validationResult = validateCustomerValue(value, 'prompts.modelAndDatasourceNameLabel');
79
108
  if (typeof validationResult === 'string') {
80
109
  return validationResult;
81
110
  }
@@ -83,149 +112,198 @@ function validatePromptODataName(value, answers, isCustomerBase, changeFiles) {
83
112
  if ((0, project_input_validator_1.hasContentDuplication)(value, 'dataSource', changeFiles)) {
84
113
  return (0, i18n_1.t)('validators.errorDuplicatedValueOData');
85
114
  }
86
- if (answers.addAnnotationMode && value === answers.dataSourceName) {
87
- return (0, i18n_1.t)('validators.errorDuplicateNamesOData');
88
- }
89
115
  return true;
90
116
  }
91
117
  /**
92
- * Validates the OData Annotation name prompt.
118
+ * Validates the OData Source URI prompt.
93
119
  *
94
120
  * @param value The value to validate.
95
- * @param answers The answers object.
96
- * @param isCustomerBase Whether the validation is for customer usage.
97
- * @param changeFiles The list of existing change files to check against.
98
- * @returns {boolean | string} True if no duplication is found, or an error message if validation fails.
121
+ * @returns {boolean | string} True if the URI is valid, or an error message if validation fails.
99
122
  */
100
- function validatePromptODataAnnotationsName(value, answers, isCustomerBase, changeFiles) {
101
- let validationResult = validatePromptInput(value);
123
+ function validatePromptURI(value) {
124
+ const validationResult = (0, project_input_validator_1.validateEmptyString)(value);
102
125
  if (typeof validationResult === 'string') {
103
126
  return validationResult;
104
127
  }
105
- if (isCustomerBase) {
106
- validationResult = validateCustomerValue(value, 'prompts.oDataAnnotationDataSourceNameLabel');
107
- if (typeof validationResult === 'string') {
108
- return validationResult;
109
- }
110
- }
111
- if ((0, project_input_validator_1.hasContentDuplication)(value, 'dataSource', changeFiles)) {
112
- return (0, i18n_1.t)('validators.errorDuplicatedValueOData');
113
- }
114
- if (value === answers.name) {
115
- return (0, i18n_1.t)('validators.errorDuplicateNamesOData');
128
+ if (!(0, project_input_validator_1.isDataSourceURI)(value)) {
129
+ return (0, i18n_1.t)('validators.errorInvalidDataSourceURI');
116
130
  }
117
131
  return true;
118
132
  }
119
133
  /**
120
- * Validates the model name prompts.
134
+ * Builds the full resulting service URL from a destination URL and a service URI.
135
+ * Returns undefined if either value is absent or the URI fails basic validation.
121
136
  *
122
- * @param value The value to validate.
123
- * @param isCustomerBase Whether the validation is for customer usage.
124
- * @param changeFiles The list of existing change files to check against.
125
- * @returns {boolean | string} True if no duplication is found, or an error message if validation fails.
137
+ * @param {string | undefined} destinationUrl - The destination base URL.
138
+ * @param {string | undefined} serviceUri - The relative service URI from the prompt.
139
+ * @returns {string | undefined} The concatenated URL, or undefined if it cannot be formed.
126
140
  */
127
- function validatePromptModelName(value, isCustomerBase, changeFiles) {
128
- let validationResult = validatePromptInput(value);
129
- if (typeof validationResult === 'string') {
130
- return validationResult;
141
+ function buildResultingServiceUrl(destinationUrl, serviceUri) {
142
+ if (!destinationUrl || !serviceUri || validatePromptURI(serviceUri) !== true) {
143
+ return undefined;
131
144
  }
132
- if (isCustomerBase) {
133
- validationResult = validateCustomerValue(value, 'prompts.oDataServiceModelNameLabel');
134
- if (typeof validationResult === 'string') {
135
- return validationResult;
145
+ return destinationUrl.replace(/\/$/, '') + serviceUri;
146
+ }
147
+ /**
148
+ * Returns the OData version string for use in change content based on the selected service type.
149
+ * Returns undefined for HTTP service type as it has no OData version.
150
+ *
151
+ * @param {ServiceType} serviceType - The selected service type.
152
+ * @returns {string | undefined} The OData version string ('2.0' or '4.0'), or undefined for HTTP.
153
+ */
154
+ function getODataVersionFromServiceType(serviceType) {
155
+ if (serviceType === types_1.ServiceType.ODATA_V2) {
156
+ return '2.0';
157
+ }
158
+ if (serviceType === types_1.ServiceType.ODATA_V4) {
159
+ return '4.0';
160
+ }
161
+ return undefined;
162
+ }
163
+ /**
164
+ * Resolves the backend base URL for ABAP (non-CF) projects.
165
+ * For VS Code projects the URL is read directly from the `target.url` field in ui5.yaml.
166
+ * For BAS projects the destination name is read from `target.destination` and the URL
167
+ * is resolved via the BAS destination service.
168
+ *
169
+ * @param {string} projectPath - The root path of the project.
170
+ * @returns {Promise<string | undefined>} The resolved base URL, or undefined if it cannot be determined.
171
+ */
172
+ async function getAbapServiceUrl(projectPath) {
173
+ try {
174
+ const { target } = (await (0, helper_1.getAdpConfig)(projectPath, 'ui5.yaml'));
175
+ if (!target) {
176
+ return undefined;
177
+ }
178
+ if (target.url) {
179
+ return target.url;
180
+ }
181
+ if (target.destination) {
182
+ const destinations = await (0, btp_utils_1.listDestinations)();
183
+ return destinations[target.destination]?.Host;
136
184
  }
137
185
  }
138
- if ((0, project_input_validator_1.hasContentDuplication)(value, 'model', changeFiles)) {
139
- return (0, i18n_1.t)('validators.errorDuplicatedValueSapui5Model');
186
+ catch {
187
+ // Message will not be shown
140
188
  }
141
- return true;
189
+ return undefined;
142
190
  }
143
191
  /**
144
- * Validates the OData Source URI prompt.
192
+ * Fetches destination choices for CF environments.
193
+ * Returns the choices and a generic UI error message if the fetch fails, logging the original error.
145
194
  *
146
- * @param value The value to validate.
147
- * @returns {boolean | string} True if the URI is valid, or an error message if validation fails.
195
+ * @param {string} projectPath - The root path of the project.
196
+ * @param {ToolsLogger} [logger] - Optional logger for error details.
197
+ * @returns {Promise<{ choices: { name: string; value: Destination }[]; error?: string }>} The destination choices and an optional error message.
148
198
  */
149
- function validatePromptURI(value) {
150
- const validationResult = (0, project_input_validator_1.validateEmptyString)(value);
151
- if (typeof validationResult === 'string') {
152
- return validationResult;
199
+ async function getDestinationChoices(projectPath, logger) {
200
+ try {
201
+ const destinations = await (0, destinations_1.getBtpDestinations)(projectPath);
202
+ const choices = Object.entries(destinations).map(([name, dest]) => ({
203
+ name,
204
+ value: dest
205
+ }));
206
+ return { choices };
153
207
  }
154
- if (!(0, project_input_validator_1.isDataSourceURI)(value)) {
155
- return (0, i18n_1.t)('validators.errorInvalidDataSourceURI');
208
+ catch (e) {
209
+ logger?.error(e.message);
210
+ return { choices: [], error: (0, i18n_1.t)('error.errorFetchingDestinations') };
156
211
  }
157
- return true;
158
212
  }
159
213
  /**
160
214
  * Gets the prompts for adding the new model.
161
215
  *
162
216
  * @param {string} projectPath - The root path of the project.
163
217
  * @param {UI5FlexLayer} layer - UI5 Flex layer.
218
+ * @param {ToolsLogger} [logger] - Optional logger.
164
219
  * @returns {YUIQuestion<NewModelAnswers>[]} The questions/prompts.
165
220
  */
166
- async function getPrompts(projectPath, layer) {
221
+ async function getPrompts(projectPath, layer, logger) {
167
222
  const isCustomerBase = "CUSTOMER_BASE" /* FlexLayer.CUSTOMER_BASE */ === layer;
168
223
  const defaultSeviceName = isCustomerBase ? "customer." /* NamespacePrefix.CUSTOMER */ : "" /* NamespacePrefix.EMPTY */;
169
224
  const isCFEnv = await (0, cf_1.isCFEnvironment)(projectPath);
170
- const changeFiles = (0, change_utils_1.getChangesByType)(projectPath, "appdescr_ui5_addNewModel" /* ChangeType.ADD_NEW_MODEL */, 'manifest');
225
+ const abapServiceUrl = isCFEnv ? undefined : await getAbapServiceUrl(projectPath);
226
+ const changeFiles = [
227
+ ...(0, change_utils_1.getChangesByType)(projectPath, "appdescr_ui5_addNewModel" /* ChangeType.ADD_NEW_MODEL */),
228
+ ...(0, change_utils_1.getChangesByType)(projectPath, "appdescr_app_addNewDataSource" /* ChangeType.ADD_NEW_DATA_SOURCE */)
229
+ ];
230
+ let destinationError;
231
+ let destinationChoices;
232
+ if (isCFEnv) {
233
+ ({ choices: destinationChoices, error: destinationError } = await getDestinationChoices(projectPath, logger));
234
+ }
235
+ const buildResultingUrlMessage = (i18nKey, uri, previousAnswers) => {
236
+ const destinationUrl = isCFEnv ? previousAnswers?.destination?.Host : abapServiceUrl;
237
+ const resultingUrl = buildResultingServiceUrl(destinationUrl, uri);
238
+ if (!resultingUrl) {
239
+ return undefined;
240
+ }
241
+ return {
242
+ message: (0, i18n_1.t)(i18nKey, { url: resultingUrl, interpolation: { escapeValue: false } }),
243
+ severity: yeoman_ui_types_1.Severity.information
244
+ };
245
+ };
171
246
  return [
172
247
  {
173
- type: 'input',
174
- name: 'name',
175
- message: (0, i18n_1.t)('prompts.oDataServiceNameLabel'),
176
- default: defaultSeviceName,
248
+ type: 'list',
249
+ name: 'serviceType',
250
+ message: (0, i18n_1.t)('prompts.serviceTypeLabel'),
251
+ choices: isCFEnv ? serviceTypeChoices : serviceTypeChoices.filter((c) => c.value !== types_1.ServiceType.HTTP),
177
252
  store: false,
178
- validate: (value, answers) => {
179
- return validatePromptODataName(value, answers, isCustomerBase, changeFiles);
180
- },
253
+ validate: project_input_validator_1.validateEmptyString,
181
254
  guiOptions: {
182
255
  mandatory: true,
183
- hint: (0, i18n_1.t)('prompts.oDataServiceNameTooltip')
256
+ hint: (0, i18n_1.t)('prompts.serviceTypeTooltip')
184
257
  }
185
258
  },
259
+ {
260
+ type: 'list',
261
+ name: 'destination',
262
+ message: (0, i18n_1.t)('prompts.destinationLabel'),
263
+ choices: () => destinationChoices ?? [],
264
+ when: () => isCFEnv,
265
+ guiOptions: {
266
+ mandatory: true,
267
+ hint: (0, i18n_1.t)('prompts.destinationTooltip')
268
+ },
269
+ validate: (value) => destinationError ?? (0, project_input_validator_1.validateEmptyString)(value?.Name)
270
+ },
186
271
  {
187
272
  type: 'input',
188
273
  name: 'uri',
189
- message: (0, i18n_1.t)('prompts.oDataServiceUriLabel'),
274
+ message: (0, i18n_1.t)('prompts.serviceUriLabel'),
190
275
  guiOptions: {
191
276
  mandatory: true,
192
277
  hint: (0, i18n_1.t)('prompts.oDataServiceUriTooltip')
193
278
  },
194
- validate: validatePromptURI,
195
- store: false
196
- },
197
- {
198
- type: 'list',
199
- name: 'version',
200
- message: (0, i18n_1.t)('prompts.oDataServiceVersionLabel'),
201
- choices: oDataVersions,
202
- default: (answers) => {
203
- if (answers.uri?.startsWith(isCFEnv ? '/odata/v4/' : '/sap/opu/odata4/')) {
204
- return oDataVersions[1].value;
279
+ validate: (value) => {
280
+ const uriResult = validatePromptURI(value);
281
+ if (typeof uriResult === 'string') {
282
+ return uriResult;
205
283
  }
206
- return oDataVersions[0].value;
284
+ if (isCFEnv && readXsAppRoutes(projectPath).some((r) => r.target === `${value}$1`)) {
285
+ return (0, i18n_1.t)('validators.errorRouteAlreadyExists');
286
+ }
287
+ return true;
207
288
  },
208
289
  store: false,
209
- validate: project_input_validator_1.validateEmptyString,
210
- guiOptions: {
211
- mandatory: true,
212
- hint: (0, i18n_1.t)('prompts.oDataServiceVersionTooltip'),
213
- applyDefaultWhenDirty: true
214
- }
290
+ additionalMessages: (uri, previousAnswers) => buildResultingUrlMessage('prompts.resultingServiceUrl', uri, previousAnswers)
215
291
  },
216
292
  {
217
293
  type: 'input',
218
- name: 'modelName',
219
- message: (0, i18n_1.t)('prompts.oDataServiceModelNameLabel'),
220
- guiOptions: {
221
- mandatory: true,
222
- hint: (0, i18n_1.t)('prompts.oDataServiceModelNameTooltip')
223
- },
294
+ name: 'modelAndDatasourceName',
295
+ message: (answers) => answers.serviceType === types_1.ServiceType.HTTP
296
+ ? (0, i18n_1.t)('prompts.datasourceNameLabel')
297
+ : (0, i18n_1.t)('prompts.modelAndDatasourceNameLabel'),
224
298
  default: defaultSeviceName,
299
+ store: false,
225
300
  validate: (value) => {
226
- return validatePromptModelName(value, isCustomerBase, changeFiles);
301
+ return validatePromptODataName(value, isCustomerBase, changeFiles);
227
302
  },
228
- store: false
303
+ guiOptions: {
304
+ mandatory: true,
305
+ hint: (0, i18n_1.t)('prompts.modelAndDatasourceNameTooltip')
306
+ }
229
307
  },
230
308
  {
231
309
  type: 'editor',
@@ -233,6 +311,7 @@ async function getPrompts(projectPath, layer) {
233
311
  message: (0, i18n_1.t)('prompts.oDataServiceModelSettingsLabel'),
234
312
  store: false,
235
313
  validate: validatePromptJSON,
314
+ when: (answers) => answers.serviceType !== types_1.ServiceType.HTTP,
236
315
  guiOptions: {
237
316
  hint: (0, i18n_1.t)('prompts.oDataServiceModelSettingsTooltip')
238
317
  }
@@ -241,22 +320,8 @@ async function getPrompts(projectPath, layer) {
241
320
  type: 'confirm',
242
321
  name: 'addAnnotationMode',
243
322
  message: 'Do you want to add annotation?',
244
- default: false
245
- },
246
- {
247
- type: 'input',
248
- name: 'dataSourceName',
249
- message: (0, i18n_1.t)('prompts.oDataAnnotationDataSourceNameLabel'),
250
- validate: (value, answers) => {
251
- return validatePromptODataAnnotationsName(value, answers, isCustomerBase, changeFiles);
252
- },
253
- default: defaultSeviceName,
254
- store: false,
255
- guiOptions: {
256
- mandatory: true,
257
- hint: (0, i18n_1.t)('prompts.oDataAnnotationDataSourceNameTooltip')
258
- },
259
- when: (answers) => answers.addAnnotationMode
323
+ default: false,
324
+ when: (answers) => answers.serviceType !== types_1.ServiceType.HTTP
260
325
  },
261
326
  {
262
327
  type: 'input',
@@ -268,7 +333,8 @@ async function getPrompts(projectPath, layer) {
268
333
  mandatory: true,
269
334
  hint: (0, i18n_1.t)('prompts.oDataAnnotationDataSourceUriTooltip')
270
335
  },
271
- when: (answers) => answers.addAnnotationMode
336
+ when: (answers) => answers.addAnnotationMode,
337
+ additionalMessages: (uri, previousAnswers) => buildResultingUrlMessage('prompts.resultingAnnotationUrl', uri, previousAnswers)
272
338
  },
273
339
  {
274
340
  type: 'editor',
@@ -283,4 +349,42 @@ async function getPrompts(projectPath, layer) {
283
349
  }
284
350
  ];
285
351
  }
352
+ /**
353
+ * Builds the NewModelData object from the prompts answers.
354
+ *
355
+ * @param {string} projectPath - The root path of the project.
356
+ * @param {DescriptorVariant} variant - The descriptor variant of the adaptation project.
357
+ * @param {NewModelAnswers} answers - The answers to the prompts.
358
+ * @param {ToolsLogger} [logger] - Optional logger instance.
359
+ * @returns {Promise<NewModelData>} The data required by NewModelWriter.
360
+ */
361
+ async function createNewModelData(projectPath, variant, answers, logger) {
362
+ const { modelAndDatasourceName, uri, serviceType, modelSettings, addAnnotationMode } = answers;
363
+ const isCloudFoundry = await (0, cf_1.isCFEnvironment)(projectPath);
364
+ return {
365
+ variant,
366
+ serviceType,
367
+ isCloudFoundry,
368
+ destinationName: isCloudFoundry ? answers.destination?.Name : undefined,
369
+ ...(isCloudFoundry &&
370
+ answers.destination && {
371
+ isOnPremiseDestination: (0, btp_utils_1.isOnPremiseDestination)(answers.destination)
372
+ }),
373
+ logger,
374
+ service: {
375
+ name: modelAndDatasourceName,
376
+ uri,
377
+ modelName: serviceType === types_1.ServiceType.HTTP ? undefined : modelAndDatasourceName,
378
+ version: getODataVersionFromServiceType(serviceType),
379
+ modelSettings
380
+ },
381
+ ...(addAnnotationMode && {
382
+ annotation: {
383
+ dataSourceName: `${modelAndDatasourceName}.annotation`,
384
+ dataSourceURI: answers.dataSourceURI,
385
+ settings: answers.annotationSettings
386
+ }
387
+ })
388
+ };
389
+ }
286
390
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  export { getPrompts as getPromptsForChangeDataSource } from './change-data-source';
2
2
  export { getPrompts as getPromptsForAddComponentUsages } from './add-component-usages';
3
- export { getPrompts as getPromptsForNewModel } from './add-new-model';
3
+ export { getPrompts as getPromptsForNewModel, getODataVersionFromServiceType, createNewModelData } from './add-new-model';
4
4
  export { getPrompts as getPromptsForChangeInbound } from './change-inbound';
5
5
  export { getPrompts as getPromptsForAddAnnotationsToOData } from './add-annotations-to-odata';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPromptsForAddAnnotationsToOData = exports.getPromptsForChangeInbound = exports.getPromptsForNewModel = exports.getPromptsForAddComponentUsages = exports.getPromptsForChangeDataSource = void 0;
3
+ exports.getPromptsForAddAnnotationsToOData = exports.getPromptsForChangeInbound = exports.createNewModelData = exports.getODataVersionFromServiceType = exports.getPromptsForNewModel = exports.getPromptsForAddComponentUsages = exports.getPromptsForChangeDataSource = void 0;
4
4
  var change_data_source_1 = require("./change-data-source");
5
5
  Object.defineProperty(exports, "getPromptsForChangeDataSource", { enumerable: true, get: function () { return change_data_source_1.getPrompts; } });
6
6
  var add_component_usages_1 = require("./add-component-usages");
7
7
  Object.defineProperty(exports, "getPromptsForAddComponentUsages", { enumerable: true, get: function () { return add_component_usages_1.getPrompts; } });
8
8
  var add_new_model_1 = require("./add-new-model");
9
9
  Object.defineProperty(exports, "getPromptsForNewModel", { enumerable: true, get: function () { return add_new_model_1.getPrompts; } });
10
+ Object.defineProperty(exports, "getODataVersionFromServiceType", { enumerable: true, get: function () { return add_new_model_1.getODataVersionFromServiceType; } });
11
+ Object.defineProperty(exports, "createNewModelData", { enumerable: true, get: function () { return add_new_model_1.createNewModelData; } });
10
12
  var change_inbound_1 = require("./change-inbound");
11
13
  Object.defineProperty(exports, "getPromptsForChangeInbound", { enumerable: true, get: function () { return change_inbound_1.getPrompts; } });
12
14
  var add_annotations_to_odata_1 = require("./add-annotations-to-odata");
@@ -13,10 +13,20 @@
13
13
  "filePathLabel": "Annotation File path",
14
14
  "filePathTooltip": "Select the annotation file from your workspace",
15
15
  "addAnnotationOdataSourceTooltip": "Select the OData service you want to add the annotation file to",
16
+ "destinationLabel": "Destination",
17
+ "destinationTooltip": "Select a destination for the service.",
18
+ "serviceTypeLabel": "Service Type",
19
+ "serviceTypeTooltip": "Select the type of service you want to add.",
16
20
  "oDataServiceNameLabel": "OData Service Name",
17
21
  "oDataServiceNameTooltip": "Enter a name for the OData service you want to add",
18
22
  "oDataServiceUriLabel": "OData Service URI",
19
23
  "oDataServiceUriTooltip": "Enter the URI for the OData service you want to add. The URI must start and end with '/' and must not contain any whitespaces or parameters",
24
+ "serviceUriLabel": "Service URI",
25
+ "resultingServiceUrl": "Resulting Service URL: {{url}}",
26
+ "resultingAnnotationUrl": "Resulting Annotation URL: {{url}}",
27
+ "modelAndDatasourceNameLabel": "Model and Data Source Name",
28
+ "modelAndDatasourceNameTooltip": "Enter a name for the data source and model.",
29
+ "datasourceNameLabel": "Data Source Name",
20
30
  "oDataServiceVersionLabel": "OData Version",
21
31
  "oDataServiceVersionTooltip": "Select the version of OData of the service you want to add",
22
32
  "oDataServiceModelNameLabel": "OData Service SAPUI5 Model Name",
@@ -67,7 +77,9 @@
67
77
  "errorDuplicateNamesOData": "An OData Service Name must be different from an OData Annotation Data Source Name. Rename and try again.",
68
78
  "errorInputInvalidValuePrefix": "{{value}} must start with '{{prefix}}'.",
69
79
  "errorCustomerEmptyValue": "{{value}} must contain at least one character in addition to '{{prefix}}'.",
80
+ "errorInputMustEndWithAlphanumeric": "The input must end with an alphanumeric character.",
70
81
  "errorInvalidDataSourceURI": "Invalid URI. The URI must start and end with '/' and contain no spaces.",
82
+ "errorRouteAlreadyExists": "A route with the same Service URI already exists in xs-app.json. Use a different URI.",
71
83
  "appDoesNotSupportManifest": "The selected application is not supported by SAPUI5 Adaptation Project because it does not have a `manifest.json` file. Please select a different application.",
72
84
  "ui5VersionNotReachableError": "The URL of the SAPUI5 version you have selected is not reachable. The <URL> URL must be made accessible through the cloud connector and the destination configuration so it can be consumed within the SAPUI5 adaptation project and its SAPUI5 Adaptation Editor.",
73
85
  "ui5VersionOutdatedError": "The SAPUI5 version you have selected is not compatible with the SAPUI5 Adaptation Editor. Please select a different version.",
@@ -115,6 +127,10 @@
115
127
  "noServiceKeysFoundForInstance": "No service keys found for service instance: {{serviceInstanceName}}",
116
128
  "couldNotFetchServiceKeys": "Cannot fetch the service keys. Error: {{error}}",
117
129
  "metadataFetchingNotSupportedForCF": "Metadata fetching is not supported for Cloud Foundry projects.",
130
+ "failedToListBtpDestinations": "Failed to list BTP destinations. Error: {{error}}",
131
+ "destinationServiceNotFoundInMtaYaml": "Destination service instance not found in mta.yaml. Ensure a resource with 'service: destination' is declared.",
132
+ "noServiceKeysFoundForDestination": "No service keys found for destination service instance '{{serviceInstanceName}}'. Ensure the service is provisioned and try again.",
133
+ "errorFetchingDestinations": "Error fetching destinations. Check log for details.",
118
134
  "cfPushFailed": "cf push failed for the '{{appName}}' app: {{error}}",
119
135
  "cfEnableSshFailed": "cf enable-ssh failed for the '{{appName}}' app: {{error}}",
120
136
  "cfRestartFailed": "cf restart failed for the '{{appName}}' app: {{error}}"
package/dist/types.d.ts CHANGED
@@ -6,6 +6,7 @@ import type { Editor } from 'mem-fs-editor';
6
6
  import type { Destination } from '@sap-ux/btp-utils';
7
7
  import type { YUIQuestion } from '@sap-ux/inquirer-common';
8
8
  import type AdmZip from 'adm-zip';
9
+ import type { ToolsLogger } from '@sap-ux/logger';
9
10
  import type { SupportedProject } from './source';
10
11
  export type DataSources = Record<string, ManifestNamespace.DataSource>;
11
12
  /**
@@ -481,12 +482,19 @@ export interface IWriter<T> {
481
482
  */
482
483
  export declare const enum ChangeType {
483
484
  ADD_NEW_MODEL = "appdescr_ui5_addNewModel",
485
+ ADD_NEW_DATA_SOURCE = "appdescr_app_addNewDataSource",
484
486
  ADD_ANNOTATIONS_TO_ODATA = "appdescr_app_addAnnotationsToOData",
485
487
  CHANGE_DATA_SOURCE = "appdescr_app_changeDataSource",
486
488
  ADD_COMPONENT_USAGES = "appdescr_ui5_addComponentUsages",
487
489
  ADD_LIBRARY_REFERENCE = "appdescr_ui5_addLibraries",
488
490
  CHANGE_INBOUND = "appdescr_app_changeInbound"
489
491
  }
492
+ export declare const ServiceType: {
493
+ readonly ODATA_V2: "OData v2";
494
+ readonly ODATA_V4: "OData v4";
495
+ readonly HTTP: "HTTP";
496
+ };
497
+ export type ServiceType = (typeof ServiceType)[keyof typeof ServiceType];
490
498
  /**
491
499
  * A mapping of ChangeType values to their respective change names.
492
500
  */
@@ -571,15 +579,25 @@ export type AddComponentUsageAnswersBase = {
571
579
  export type AddComponentUsageAnswers = AddComponentUsageAnswersBase & (AddComponentUsageAnswersWithoutLibrary | addComponentUsageAnswersWithLibrary);
572
580
  export interface NewModelDataBase {
573
581
  variant: DescriptorVariant;
582
+ /** The type of service being added. Determines dataSource type and whether a model entry is created. */
583
+ serviceType: ServiceType;
584
+ /** Whether the project is deployed on Cloud Foundry. Affects URI construction and model settings. */
585
+ isCloudFoundry?: boolean;
586
+ /** Name of the BTP destination. Required when isCloudFoundry is true to write the xs-app.json route. */
587
+ destinationName?: string;
588
+ /** True when the selected CF destination is OnPremise (ProxyType: 'OnPremise'). Triggers connectivity service in mta.yaml. */
589
+ isOnPremiseDestination?: boolean;
590
+ /** Optional logger passed to the writer for use during the write operation. */
591
+ logger?: ToolsLogger;
574
592
  service: {
575
593
  /** Name of the OData service. */
576
594
  name: string;
577
595
  /** URI of the OData service. */
578
596
  uri: string;
579
- /** Name of the OData service model. */
580
- modelName: string;
581
- /** Version of OData used. */
582
- version: string;
597
+ /** Name of the OData service model. Optional for absent for HTTP service type. */
598
+ modelName?: string;
599
+ /** Version of OData used. Optional for HTTP service type. */
600
+ version?: string;
583
601
  /** Settings for the OData service model. */
584
602
  modelSettings?: string;
585
603
  };
@@ -596,21 +614,19 @@ export interface NewModelDataWithAnnotations extends NewModelDataBase {
596
614
  }
597
615
  export type NewModelData = NewModelDataBase | NewModelDataWithAnnotations;
598
616
  export interface NewModelAnswersBase {
599
- /** Name of the OData service. */
600
- name: string;
617
+ /** Selected BTP destination. Only relevant for CF projects. */
618
+ destination?: Destination;
619
+ /** Type of service to add. */
620
+ serviceType: ServiceType;
621
+ /** Name used for both the OData service datasource and the SAPUI5 model. */
622
+ modelAndDatasourceName: string;
601
623
  /** URI of the OData service. */
602
624
  uri: string;
603
- /** Name of the OData service model. */
604
- modelName: string;
605
- /** Version of OData used. */
606
- version: string;
607
625
  /** Settings for the OData service model. */
608
626
  modelSettings: string;
609
627
  }
610
628
  export interface NewModelAnswersWithAnnotations extends NewModelAnswersBase {
611
629
  addAnnotationMode: true;
612
- /** Name of the OData annotation data source. */
613
- dataSourceName: string;
614
630
  /** Optional URI of the OData annotation data source. */
615
631
  dataSourceURI?: string;
616
632
  /** Optional settings for the OData annotation. */
@@ -794,6 +810,12 @@ export interface Uaa {
794
810
  clientsecret: string;
795
811
  url: string;
796
812
  }
813
+ export type CfDestinationServiceCredentials = {
814
+ uri: string;
815
+ uaa: Uaa;
816
+ } | ({
817
+ uri: string;
818
+ } & Uaa);
797
819
  export interface CfAppParams {
798
820
  appName: string;
799
821
  appVersion: string;
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cfServicesPromptNames = exports.AppRouterType = exports.ChangeTypeMap = exports.ApplicationType = void 0;
3
+ exports.cfServicesPromptNames = exports.AppRouterType = exports.ChangeTypeMap = exports.ServiceType = exports.ApplicationType = void 0;
4
4
  var ApplicationType;
5
5
  (function (ApplicationType) {
6
6
  ApplicationType["FIORI_ELEMENTS"] = "FioriElements";
@@ -8,11 +8,17 @@ var ApplicationType;
8
8
  ApplicationType["FREE_STYLE"] = "FreeStyle";
9
9
  ApplicationType["NONE"] = "";
10
10
  })(ApplicationType || (exports.ApplicationType = ApplicationType = {}));
11
+ exports.ServiceType = {
12
+ ODATA_V2: 'OData v2',
13
+ ODATA_V4: 'OData v4',
14
+ HTTP: 'HTTP'
15
+ };
11
16
  /**
12
17
  * A mapping of ChangeType values to their respective change names.
13
18
  */
14
19
  exports.ChangeTypeMap = {
15
20
  ["appdescr_ui5_addNewModel" /* ChangeType.ADD_NEW_MODEL */]: 'addNewModel',
21
+ ["appdescr_app_addNewDataSource" /* ChangeType.ADD_NEW_DATA_SOURCE */]: 'addNewDataSource',
16
22
  ["appdescr_app_addAnnotationsToOData" /* ChangeType.ADD_ANNOTATIONS_TO_ODATA */]: 'addAnnotationsToOData',
17
23
  ["appdescr_app_changeDataSource" /* ChangeType.CHANGE_DATA_SOURCE */]: 'changeDataSource',
18
24
  ["appdescr_ui5_addComponentUsages" /* ChangeType.ADD_COMPONENT_USAGES */]: 'addComponentUsages',
@@ -1,6 +1,6 @@
1
1
  import { type Editor } from 'mem-fs-editor';
2
2
  import { type ToolsLogger } from '@sap-ux/logger';
3
- import type { CfAdpWriterConfig, CfUi5AppInfo, CfConfig } from '../types';
3
+ import type { CfAdpWriterConfig, CfConfig, CfUi5AppInfo } from '../types';
4
4
  /**
5
5
  * Writes the CF adp-project template to the mem-fs-editor instance.
6
6
  *
package/dist/writer/cf.js CHANGED
@@ -16,8 +16,9 @@ const source_1 = require("../source");
16
16
  const manifest_1 = require("./manifest");
17
17
  const project_utils_1 = require("./project-utils");
18
18
  const i18n_1 = require("./i18n");
19
- const helper_1 = require("../base/helper");
20
19
  const project_builder_1 = require("../base/project-builder");
20
+ const helper_1 = require("../base/helper");
21
+ const discovery_1 = require("../cf/app/discovery");
21
22
  /**
22
23
  * Writes the CF adp-project template to the mem-fs-editor instance.
23
24
  *
@@ -118,7 +119,7 @@ async function setupCfPreview(basePath, yamlPath, cfConfig, logger) {
118
119
  throw new Error(`No service keys found for service instance: ${serviceInstanceName}`);
119
120
  }
120
121
  const appId = await (0, helper_1.getBaseAppId)(basePath);
121
- const appHostIds = (0, cf_1.getAppHostIds)(serviceInfo.serviceKeys);
122
+ const appHostIds = (0, discovery_1.getAppHostIds)(serviceInfo.serviceKeys);
122
123
  const ui5AppInfo = await (0, cf_1.getCfUi5AppInfo)(appId, appHostIds, cfConfig, logger);
123
124
  if (appHostIds.length === 0) {
124
125
  throw new Error('No app host IDs found in service keys.');
@@ -25,5 +25,12 @@ export declare class NewModelWriter implements IWriter<NewModelData> {
25
25
  * @returns {Promise<void>} A promise that resolves when the change writing process is completed.
26
26
  */
27
27
  write(data: NewModelData): Promise<void>;
28
+ /**
29
+ * Creates or updates the xs-app.json in the webapp folder with a new AppRouter route
30
+ * for the added OData service.
31
+ *
32
+ * @param {NewModelData} data - The new model data containing service name, URI and destination name.
33
+ */
34
+ private writeXsAppRoute;
28
35
  }
29
36
  //# sourceMappingURL=new-model-writer.d.ts.map
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NewModelWriter = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const logger_1 = require("@sap-ux/logger");
6
+ const types_1 = require("../../../types");
4
7
  const change_utils_1 = require("../../../base/change-utils");
8
+ const yaml_1 = require("../../../cf/project/yaml");
9
+ const ssh_1 = require("../../../cf/services/ssh");
5
10
  /**
6
11
  * Handles the creation and writing of new sapui5 model data changes for a project.
7
12
  */
@@ -23,29 +28,35 @@ class NewModelWriter {
23
28
  * @returns {object} The constructed content object for the new model change.
24
29
  */
25
30
  constructContent(data) {
26
- const { service } = data;
31
+ const { service, isCloudFoundry, serviceType } = data;
32
+ const isHttp = serviceType === types_1.ServiceType.HTTP;
33
+ const uri = isCloudFoundry ? `${service.name.replaceAll('.', '/')}${service.uri}` : service.uri;
34
+ const dataSourceEntry = {
35
+ uri,
36
+ type: isHttp ? 'http' : 'OData',
37
+ settings: {}
38
+ };
39
+ if (service.version) {
40
+ dataSourceEntry.settings.odataVersion = service.version;
41
+ }
27
42
  const content = {
28
43
  dataSource: {
29
- [service.name]: {
30
- uri: service.uri,
31
- type: 'OData',
32
- settings: {
33
- odataVersion: service.version
34
- }
35
- }
36
- },
37
- model: {
38
- [service.modelName]: {
39
- dataSource: service.name
40
- }
44
+ [service.name]: dataSourceEntry
41
45
  }
42
46
  };
43
- if (service.modelSettings && service.modelSettings.length !== 0) {
44
- content.model[service.modelName].settings = (0, change_utils_1.parseStringToObject)(service.modelSettings);
47
+ if (!isHttp && service.modelName) {
48
+ content.model = {
49
+ [service.modelName]: {
50
+ dataSource: service.name,
51
+ ...(service.modelSettings?.length ? { settings: (0, change_utils_1.parseStringToObject)(service.modelSettings) } : {})
52
+ }
53
+ };
45
54
  }
46
55
  if ('annotation' in data) {
47
56
  const { annotation } = data;
48
- content.dataSource[service.name].settings.annotations = [`${annotation.dataSourceName}`];
57
+ content.dataSource[service.name].settings.annotations = [
58
+ `${annotation.dataSourceName}`
59
+ ];
49
60
  content.dataSource[annotation.dataSourceName] = {
50
61
  uri: annotation.dataSourceURI,
51
62
  type: 'ODataAnnotation'
@@ -64,9 +75,35 @@ class NewModelWriter {
64
75
  */
65
76
  async write(data) {
66
77
  const timestamp = Date.now();
78
+ const isHttp = data.serviceType === types_1.ServiceType.HTTP;
67
79
  const content = this.constructContent(data);
68
- const change = (0, change_utils_1.getChange)(data.variant, timestamp, content, "appdescr_ui5_addNewModel" /* ChangeType.ADD_NEW_MODEL */);
80
+ const change = (0, change_utils_1.getChange)(data.variant, timestamp, content, isHttp ? "appdescr_app_addNewDataSource" /* ChangeType.ADD_NEW_DATA_SOURCE */ : "appdescr_ui5_addNewModel" /* ChangeType.ADD_NEW_MODEL */);
69
81
  await (0, change_utils_1.writeChangeToFolder)(this.projectPath, change, this.fs);
82
+ if (data.isCloudFoundry) {
83
+ this.writeXsAppRoute(data);
84
+ }
85
+ if (data.isOnPremiseDestination) {
86
+ await (0, yaml_1.addConnectivityServiceToMta)((0, node_path_1.dirname)(this.projectPath), this.fs);
87
+ await (0, ssh_1.ensureTunnelAppExists)(ssh_1.DEFAULT_TUNNEL_APP_NAME, data.logger ?? new logger_1.ToolsLogger());
88
+ }
89
+ }
90
+ /**
91
+ * Creates or updates the xs-app.json in the webapp folder with a new AppRouter route
92
+ * for the added OData service.
93
+ *
94
+ * @param {NewModelData} data - The new model data containing service name, URI and destination name.
95
+ */
96
+ writeXsAppRoute(data) {
97
+ const xsAppPath = (0, node_path_1.join)(this.projectPath, 'webapp', 'xs-app.json');
98
+ const source = `^/${data.service.name.replaceAll('.', '/')}${data.service.uri}(.*)`;
99
+ const newRoute = {
100
+ source,
101
+ target: `${data.service.uri}$1`,
102
+ destination: data.destinationName
103
+ };
104
+ const existing = this.fs.readJSON(xsAppPath, { routes: [] });
105
+ existing.routes.push(newRoute);
106
+ this.fs.writeJSON(xsAppPath, existing);
70
107
  }
71
108
  }
72
109
  exports.NewModelWriter = NewModelWriter;
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.18.116",
12
+ "version": "0.18.118",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "@sap-ux/axios-extension": "1.25.31",
40
40
  "@sap-ux/btp-utils": "1.1.14",
41
41
  "@sap-ux/i18n": "0.3.10",
42
- "@sap-ux/inquirer-common": "0.11.36",
42
+ "@sap-ux/inquirer-common": "0.11.37",
43
43
  "@sap-ux/logger": "0.8.5",
44
44
  "@sap-ux/nodejs-utils": "0.2.21",
45
45
  "@sap-ux/odata-service-writer": "0.31.7",
@@ -63,7 +63,7 @@
63
63
  "@types/uuid": "11.0.0",
64
64
  "adm-zip": "0.5.16",
65
65
  "cross-env": "10.1.0",
66
- "dotenv": "17.3.1",
66
+ "dotenv": "17.4.2",
67
67
  "express": "4.22.1",
68
68
  "nock": "14.0.11",
69
69
  "rimraf": "6.1.3",