@sap-ux/cf-deploy-config-writer 0.3.63 → 0.3.65

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.
@@ -26,9 +26,7 @@ const i18n_1 = require("../i18n");
26
26
  * @returns file system reference
27
27
  */
28
28
  async function generateAppConfig(cfAppConfig, fs, logger) {
29
- if (!fs) {
30
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
31
- }
29
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
32
30
  if (logger) {
33
31
  logger_helper_1.default.logger = logger;
34
32
  }
@@ -55,16 +53,16 @@ async function getUpdatedConfig(cfAppConfig, fs) {
55
53
  (0, utils_1.enforceValidRouterConfig)(cfAppConfig);
56
54
  const config = {
57
55
  appPath: cfAppConfig.appPath.replace(/\/$/, ''),
58
- destinationName: cfAppConfig.destinationName || destination,
56
+ destinationName: cfAppConfig.destinationName ?? destination,
59
57
  addManagedAppRouter: cfAppConfig.addManagedAppRouter,
60
58
  addAppFrontendRouter: cfAppConfig.addAppFrontendRouter,
61
59
  addMtaDestination: cfAppConfig.addMtaDestination ?? false,
62
60
  cloudServiceName: cfAppConfig.cloudServiceName,
63
61
  lcapMode: !isCap ? false : isLCAP, // Restricting local changes is only applicable for CAP flows
64
62
  isMtaRoot: hasRoot ?? false,
65
- serviceHost: cfAppConfig.serviceHost || serviceHost,
63
+ serviceHost: cfAppConfig.serviceHost ?? serviceHost,
66
64
  rootPath: rootPath.replace(/\/$/, ''),
67
- destinationAuthentication: cfAppConfig.destinationAuthentication || destinationAuthentication,
65
+ destinationAuthentication: cfAppConfig.destinationAuthentication ?? destinationAuthentication,
68
66
  isDestinationFullUrl: cfAppConfig.isDestinationFullUrl ?? destinationIsFullUrl,
69
67
  apiHubConfig: cfAppConfig.apiHubConfig,
70
68
  firstServicePathSegment: firstServicePathSegmentUI5Config ?? firstServicePathSegment ?? '<apply-service-segment-path>',
@@ -138,10 +136,11 @@ async function processUI5Config(appPath, fs) {
138
136
  * @returns servicePath, firstServicePathSegment and appId properties
139
137
  */
140
138
  async function processManifest(appPath, fs) {
141
- const manifest = await (0, utils_1.readManifest)((0, node_path_1.join)(await (0, project_access_1.getWebappPath)(appPath), project_access_1.FileName.Manifest), fs);
142
- const appId = manifest?.['sap.app']?.id ? (0, mta_config_1.toMtaModuleName)(manifest?.['sap.app']?.id) : undefined;
139
+ const webappPath = await (0, project_access_1.getWebappPath)(appPath);
140
+ const manifest = (0, utils_1.readManifest)((0, node_path_1.join)(webappPath, project_access_1.FileName.Manifest), fs);
141
+ const appId = manifest?.['sap.app']?.id ? (0, mta_config_1.toMtaModuleName)(manifest['sap.app'].id) : undefined;
143
142
  const servicePath = manifest?.['sap.app']?.dataSources?.mainService?.uri;
144
- const firstServicePathSegment = servicePath?.substring(0, servicePath?.indexOf('/', 1));
143
+ const firstServicePathSegment = servicePath?.substring(0, servicePath.indexOf('/', 1));
145
144
  return { servicePath, firstServicePathSegment, appId };
146
145
  }
147
146
  /**
@@ -307,16 +306,18 @@ async function appendCloudFoundryConfigurations(cfConfig, fs) {
307
306
  }
308
307
  /**
309
308
  * Updates the manifest.json file with the cloud service name.
309
+ * Preserves existing sap.cloud properties while updating public and service values.
310
310
  *
311
311
  * @param cfConfig writer configuration
312
312
  * @param fs reference to a mem-fs editor
313
313
  */
314
314
  async function updateManifest(cfConfig, fs) {
315
315
  const webappPath = await (0, project_access_1.getWebappPath)(cfConfig.appPath, fs);
316
- const manifest = await (0, utils_1.readManifest)((0, node_path_1.join)(webappPath, project_access_1.FileName.Manifest), fs);
316
+ const manifest = (0, utils_1.readManifest)((0, node_path_1.join)(webappPath, project_access_1.FileName.Manifest), fs);
317
317
  if (manifest && cfConfig.cloudServiceName) {
318
+ // Preserve existing sap.cloud properties while updating required values (Sonar S7744 fix)
318
319
  const sapCloud = {
319
- ...(manifest['sap.cloud'] || {}),
320
+ ...manifest['sap.cloud'],
320
321
  public: true,
321
322
  service: cfConfig.cloudServiceName
322
323
  };
@@ -20,9 +20,7 @@ const i18n_1 = require("../i18n");
20
20
  * @returns file system reference
21
21
  */
22
22
  async function generateBaseConfig(config, fs, logger) {
23
- if (!fs) {
24
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
25
- }
23
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
26
24
  if (logger) {
27
25
  logger_helper_1.default.logger = logger;
28
26
  }
@@ -11,6 +11,7 @@ const mta_config_1 = require("../mta-config");
11
11
  const logger_helper_1 = __importDefault(require("../logger-helper"));
12
12
  const i18n_1 = require("../i18n");
13
13
  const project_access_1 = require("@sap-ux/project-access");
14
+ const constants_1 = require("../constants");
14
15
  /**
15
16
  * Add a standalone | managed approuter to a CAP project.
16
17
  *
@@ -20,17 +21,15 @@ const project_access_1 = require("@sap-ux/project-access");
20
21
  * @returns file system reference
21
22
  */
22
23
  async function generateCAPConfig(config, fs, logger) {
23
- if (!fs) {
24
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
25
- }
24
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
26
25
  if (logger) {
27
26
  logger_helper_1.default.logger = logger;
28
27
  }
29
28
  logger?.debug(`Generate CAP configuration using: \n ${JSON.stringify(config)}`);
30
29
  await validateConfig(config);
31
30
  await (0, mta_config_1.generateCAPMTA)(config, fs);
32
- // Delay, known issues with loading mta yaml after generation!
33
- await new Promise((resolve) => setTimeout(resolve, 1000));
31
+ // Delay to ensure MTA file write completes before subsequent read operations
32
+ await new Promise((resolve) => setTimeout(resolve, constants_1.MTA_FILE_OPERATION_DELAY_MS));
34
33
  await (0, mta_config_1.addRoutingConfig)(config, fs);
35
34
  await (0, utils_1.updateRootPackage)({ mtaId: config.mtaId, rootPath: config.mtaPath }, fs);
36
35
  logger_helper_1.default.logger?.debug((0, i18n_1.t)('debug.capGenerationCompleted'));
@@ -118,4 +118,11 @@ export declare const rootDeployMTAScript: (args: string[]) => string;
118
118
  export declare const undeployMTAScript: (mtaId: string) => string;
119
119
  export declare const CDSBinNotFound: string;
120
120
  export declare const MTABinNotFound: string;
121
+ export declare const MAX_MTA_ID_LENGTH = 128;
122
+ export declare const MAX_MTA_PREFIX_LENGTH = 100;
123
+ export declare const MAX_MTA_PREFIX_SHORT_LENGTH = 94;
124
+ export declare const MAX_MTA_PREFIX_SHORTER_LENGTH = 96;
125
+ export declare const MAX_ABAP_SERVICE_PREFIX_LENGTH = 24;
126
+ export declare const MAX_ABAP_SERVICE_NAME_LENGTH = 20;
127
+ export declare const MTA_FILE_OPERATION_DELAY_MS = 1000;
121
128
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CDSBinNotFound = exports.undeployMTAScript = exports.rootDeployMTAScript = exports.appDeployMTAScript = exports.MTABuildScript = exports.UI5DeployBuildScript = exports.UI5DeployBuildScriptForCap = exports.HTMLAppBuildParams = exports.ServiceAPIRequires = exports.DestinationServiceConfig = exports.UI5StandaloneModuleDestination = exports.UI5ResourceDestination = exports.CAPAppfrontDestination = exports.UI5AppfrontDestinationParameter = exports.UI5Destination = exports.MTAAPIDestination = exports.CDSDestinationService = exports.CDSHTML5RepoService = exports.CDSXSUAAService = exports.CDSAddMtaParams = exports.UI5PackageVersion = exports.UI5Package = exports.UI5TaskZipperPackageVersion = exports.UI5TaskZipperPackage = exports.MbtPackageVersion = exports.MbtPackage = exports.Rimraf = exports.RimrafVersion = exports.MTAVersion = exports.MTAPackage = exports.MTAExecutable = exports.CDSPackage = exports.CDSDKPackage = exports.CDSExecutable = exports.enableParallelDeployments = exports.deployMode = exports.CloudFoundry = exports.RouterModule = exports.MTADescription = exports.EmptyDestination = exports.DefaultMTADestination = exports.SRV_API = exports.HTML5RepoHost = exports.ManagedDestination = exports.ManagedAppFront = exports.ManagedXSUAA = exports.DefaultServiceURL = exports.MTABuildParams = exports.MTABuildResult = exports.WelcomeFile = void 0;
4
- exports.MTABinNotFound = void 0;
4
+ exports.MTA_FILE_OPERATION_DELAY_MS = exports.MAX_ABAP_SERVICE_NAME_LENGTH = exports.MAX_ABAP_SERVICE_PREFIX_LENGTH = exports.MAX_MTA_PREFIX_SHORTER_LENGTH = exports.MAX_MTA_PREFIX_SHORT_LENGTH = exports.MAX_MTA_PREFIX_LENGTH = exports.MAX_MTA_ID_LENGTH = exports.MTABinNotFound = void 0;
5
5
  const i18n_1 = require("./i18n");
6
6
  exports.WelcomeFile = 'welcomeFile';
7
7
  exports.MTABuildResult = 'build-result';
@@ -123,4 +123,15 @@ exports.undeployMTAScript = undeployMTAScript;
123
123
  const cannotFindBinary = (bin, pkg) => (0, i18n_1.t)('error.cannotFindBinary', { bin, pkg });
124
124
  exports.CDSBinNotFound = cannotFindBinary(exports.CDSExecutable, exports.CDSDKPackage);
125
125
  exports.MTABinNotFound = cannotFindBinary(exports.MTAExecutable, exports.MTAPackage);
126
+ // MTA ID length limits
127
+ exports.MAX_MTA_ID_LENGTH = 128;
128
+ exports.MAX_MTA_PREFIX_LENGTH = 100;
129
+ exports.MAX_MTA_PREFIX_SHORT_LENGTH = 94;
130
+ exports.MAX_MTA_PREFIX_SHORTER_LENGTH = 96;
131
+ exports.MAX_ABAP_SERVICE_PREFIX_LENGTH = 24;
132
+ exports.MAX_ABAP_SERVICE_NAME_LENGTH = 20;
133
+ // MTA file operation timing
134
+ // Delay in milliseconds to ensure MTA file operations complete before subsequent reads
135
+ // This is necessary as mta-lib requires files to be fully written to disk before access
136
+ exports.MTA_FILE_OPERATION_DELAY_MS = 1000;
126
137
  //# sourceMappingURL=constants.js.map
package/dist/i18n.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { i18n as i18nNext, TOptions } from 'i18next';
2
2
  export declare const i18n: i18nNext;
3
3
  /**
4
4
  * Initialize i18next with the translations for this module.
5
+ *
6
+ * @returns {Promise<void>} A promise that resolves when i18n is initialized
5
7
  */
6
8
  export declare function initI18n(): Promise<void>;
7
9
  /**
package/dist/i18n.js CHANGED
@@ -12,6 +12,8 @@ const NS = 'cf-deploy-config-writer';
12
12
  exports.i18n = i18next_1.default.createInstance();
13
13
  /**
14
14
  * Initialize i18next with the translations for this module.
15
+ *
16
+ * @returns {Promise<void>} A promise that resolves when i18n is initialized
15
17
  */
16
18
  async function initI18n() {
17
19
  await exports.i18n.init({
@@ -36,6 +38,8 @@ async function initI18n() {
36
38
  function t(key, options) {
37
39
  return exports.i18n.t(key, options);
38
40
  }
41
+ // Initialize i18n on module load
42
+ // Errors are ignored since the writer will still work (fallback strings will be used)
39
43
  initI18n().catch(() => {
40
44
  // Ignore any errors since the write will still work
41
45
  });
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './mta-config';
2
2
  export * from './cf-writer';
3
- export { DefaultMTADestination } from './constants';
3
+ export { DefaultMTADestination, MAX_MTA_ID_LENGTH, MAX_MTA_PREFIX_LENGTH, MAX_MTA_PREFIX_SHORT_LENGTH, MAX_MTA_PREFIX_SHORTER_LENGTH, MAX_ABAP_SERVICE_PREFIX_LENGTH, MAX_ABAP_SERVICE_NAME_LENGTH, MTA_FILE_OPERATION_DELAY_MS } from './constants';
4
4
  export { CFBaseConfig, CFAppConfig, CAPConfig, RouterModuleType, ApiHubConfig, ApiHubType } from './types';
5
5
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,11 +14,18 @@ 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.RouterModuleType = exports.DefaultMTADestination = void 0;
17
+ exports.RouterModuleType = exports.MTA_FILE_OPERATION_DELAY_MS = exports.MAX_ABAP_SERVICE_NAME_LENGTH = exports.MAX_ABAP_SERVICE_PREFIX_LENGTH = exports.MAX_MTA_PREFIX_SHORTER_LENGTH = exports.MAX_MTA_PREFIX_SHORT_LENGTH = exports.MAX_MTA_PREFIX_LENGTH = exports.MAX_MTA_ID_LENGTH = exports.DefaultMTADestination = void 0;
18
18
  __exportStar(require("./mta-config"), exports);
19
19
  __exportStar(require("./cf-writer"), exports);
20
20
  var constants_1 = require("./constants");
21
21
  Object.defineProperty(exports, "DefaultMTADestination", { enumerable: true, get: function () { return constants_1.DefaultMTADestination; } });
22
+ Object.defineProperty(exports, "MAX_MTA_ID_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_MTA_ID_LENGTH; } });
23
+ Object.defineProperty(exports, "MAX_MTA_PREFIX_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_MTA_PREFIX_LENGTH; } });
24
+ Object.defineProperty(exports, "MAX_MTA_PREFIX_SHORT_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_MTA_PREFIX_SHORT_LENGTH; } });
25
+ Object.defineProperty(exports, "MAX_MTA_PREFIX_SHORTER_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_MTA_PREFIX_SHORTER_LENGTH; } });
26
+ Object.defineProperty(exports, "MAX_ABAP_SERVICE_PREFIX_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_ABAP_SERVICE_PREFIX_LENGTH; } });
27
+ Object.defineProperty(exports, "MAX_ABAP_SERVICE_NAME_LENGTH", { enumerable: true, get: function () { return constants_1.MAX_ABAP_SERVICE_NAME_LENGTH; } });
28
+ Object.defineProperty(exports, "MTA_FILE_OPERATION_DELAY_MS", { enumerable: true, get: function () { return constants_1.MTA_FILE_OPERATION_DELAY_MS; } });
22
29
  var types_1 = require("./types");
23
30
  Object.defineProperty(exports, "RouterModuleType", { enumerable: true, get: function () { return types_1.RouterModuleType; } });
24
31
  //# sourceMappingURL=index.js.map
@@ -9,17 +9,19 @@ import { type Editor } from 'mem-fs-editor';
9
9
  */
10
10
  export declare function getMtaId(rootPath: string): Promise<string | undefined>;
11
11
  /**
12
- * Get the MTA configuration from the target folder.
12
+ * Get the MTA configuration from the target folder.
13
+ * Retries up to 5 times with delays to handle file system timing issues.
13
14
  *
14
15
  * @param rootPath Path to the root folder
15
16
  * @returns MtaConfig instance if found
16
17
  */
17
18
  export declare function getMtaConfig(rootPath: string): Promise<MtaConfig | undefined>;
18
19
  /**
19
- * Generate an MTA ID that is suitable for CF deployment.
20
+ * Generate an MTA ID that is suitable for CF deployment.
21
+ * Removes special characters and restricts length to maximum allowed.
20
22
  *
21
- * @param appId Name of the app, like `sap.ux.app` and restrict to 128 characters
22
- * @returns Name that's acceptable for mta.yaml
23
+ * @param appId Name of the app, like `sap.ux.app`
24
+ * @returns Name that's acceptable for mta.yaml (sanitized and length-restricted)
23
25
  */
24
26
  export declare function toMtaModuleName(appId: string): string;
25
27
  /**
@@ -42,6 +44,7 @@ export declare function doesCDSBinaryExist(): void;
42
44
  * Validate the writer configuration to ensure all required parameters are present.
43
45
  *
44
46
  * @param config writer configuration
47
+ * @throws {Error} If validation fails
45
48
  */
46
49
  export declare function validateMtaConfig(config: CFBaseConfig): void;
47
50
  /**
@@ -48,14 +48,16 @@ async function getMtaId(rootPath) {
48
48
  return (await getMtaConfig(rootPath))?.prefix;
49
49
  }
50
50
  /**
51
- * Get the MTA configuration from the target folder.
51
+ * Get the MTA configuration from the target folder.
52
+ * Retries up to 5 times with delays to handle file system timing issues.
52
53
  *
53
54
  * @param rootPath Path to the root folder
54
55
  * @returns MtaConfig instance if found
55
56
  */
56
57
  async function getMtaConfig(rootPath) {
57
58
  let mtaConfig;
58
- for (let retries = 5; retries >= 0; retries--) {
59
+ const MAX_RETRIES = 5;
60
+ for (let retries = MAX_RETRIES; retries >= 0; retries--) {
59
61
  try {
60
62
  mtaConfig = await mta_1.MtaConfig.newInstance(rootPath, logger_helper_1.default.logger);
61
63
  if (mtaConfig?.prefix) {
@@ -64,20 +66,25 @@ async function getMtaConfig(rootPath) {
64
66
  }
65
67
  catch (error) {
66
68
  logger_helper_1.default.logger?.debug((0, i18n_1.t)('debug.errorReadingMta', { error: error.message }));
67
- await new Promise((resolve) => setTimeout(resolve, 1000));
69
+ // Delay before retry to allow file system operations to complete
70
+ await new Promise((resolve) => setTimeout(resolve, constants_1.MTA_FILE_OPERATION_DELAY_MS));
68
71
  }
69
72
  }
70
73
  logger_helper_1.default.logger?.debug((0, i18n_1.t)('debug.mtaReadWithPrefix', { prefix: mtaConfig?.prefix }));
71
74
  return mtaConfig;
72
75
  }
73
76
  /**
74
- * Generate an MTA ID that is suitable for CF deployment.
77
+ * Generate an MTA ID that is suitable for CF deployment.
78
+ * Removes special characters and restricts length to maximum allowed.
75
79
  *
76
- * @param appId Name of the app, like `sap.ux.app` and restrict to 128 characters
77
- * @returns Name that's acceptable for mta.yaml
80
+ * @param appId Name of the app, like `sap.ux.app`
81
+ * @returns Name that's acceptable for mta.yaml (sanitized and length-restricted)
78
82
  */
79
83
  function toMtaModuleName(appId) {
80
- return appId.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>]/gi, '').slice(0, 128);
84
+ // Remove special characters not allowed in MTA module names
85
+ // MTA IDs must contain only alphanumeric characters, hyphens, underscores, and dots
86
+ // Using replaceAll for global replacement (Sonar S7781)
87
+ return appId.replaceAll(/[`~!@#$%^&*()_|+\-=?;:'",.<>]/gi, '').slice(0, constants_1.MAX_MTA_ID_LENGTH);
81
88
  }
82
89
  /**
83
90
  * Create an MTA file in the target folder, needs to be written to disk as subsequent calls are dependent on it being on the file system i.e mta-lib.
@@ -85,7 +92,7 @@ function toMtaModuleName(appId) {
85
92
  * @param config writer configuration
86
93
  */
87
94
  function createMTA(config) {
88
- const mtaId = `${config.mtaId.slice(0, 128)}`;
95
+ const mtaId = `${config.mtaId.slice(0, constants_1.MAX_MTA_ID_LENGTH)}`;
89
96
  const mtaTemplate = (0, node_fs_1.readFileSync)((0, utils_1.getTemplatePath)(`app/${project_access_1.FileName.MtaYaml}`), 'utf-8');
90
97
  const mtaContents = (0, ejs_1.render)(mtaTemplate, {
91
98
  id: mtaId,
@@ -121,6 +128,7 @@ function doesCDSBinaryExist() {
121
128
  * Validate the writer configuration to ensure all required parameters are present.
122
129
  *
123
130
  * @param config writer configuration
131
+ * @throws {Error} If validation fails
124
132
  */
125
133
  function validateMtaConfig(config) {
126
134
  // We use mta-lib, which in turn relies on the mta executable being installed and available in the path
@@ -128,7 +136,7 @@ function validateMtaConfig(config) {
128
136
  if (!config.routerType || !config.mtaId || !config.mtaPath) {
129
137
  throw new Error((0, i18n_1.t)('error.missingMtaParameters'));
130
138
  }
131
- if (config.mtaId.length > 128 || !/^[a-zA-Z_]/.test(config.mtaId)) {
139
+ if (config.mtaId.length > constants_1.MAX_MTA_ID_LENGTH || !/^[a-zA-Z_]/.test(config.mtaId)) {
132
140
  throw new Error((0, i18n_1.t)('error.invalidMtaId'));
133
141
  }
134
142
  if (!/^[\w\-.]*$/.test(config.mtaId)) {
@@ -153,7 +161,7 @@ function validateMtaConfig(config) {
153
161
  async function createCAPMTAAppFrontend(config, fs) {
154
162
  const mtaTemplate = (0, node_fs_1.readFileSync)((0, utils_1.getTemplatePath)(`frontend/${project_access_1.FileName.MtaYaml}`), 'utf-8');
155
163
  const mtaContents = (0, ejs_1.render)(mtaTemplate, {
156
- id: `${config.mtaId.slice(0, 128)}`,
164
+ id: `${config.mtaId.slice(0, constants_1.MAX_MTA_ID_LENGTH)}`,
157
165
  mtaDescription: config.mtaDescription ?? constants_1.MTADescription,
158
166
  mtaVersion: config.mtaVersion ?? constants_1.MTAVersion
159
167
  });
@@ -135,7 +135,7 @@ class MtaConfig {
135
135
  const appHostName = this.resources.get(constants_1.HTML5RepoHost)?.name;
136
136
  if (appHostName) {
137
137
  const appContentModule = {
138
- name: `${this.prefix?.slice(0, 100)}-app-content`,
138
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-app-content`,
139
139
  type: 'com.sap.application.content',
140
140
  path: '.',
141
141
  requires: [
@@ -158,12 +158,15 @@ class MtaConfig {
158
158
  }
159
159
  async addUaa() {
160
160
  const resource = {
161
- name: `${this.prefix?.slice(0, 100)}-uaa`,
161
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-uaa`,
162
162
  type: 'org.cloudfoundry.managed-service',
163
163
  parameters: {
164
164
  'service-plan': 'application',
165
165
  service: 'xsuaa',
166
- config: { xsappname: `${this.prefix?.slice(0, 100)}` + '-${space-guid}', 'tenant-mode': 'dedicated' }
166
+ config: {
167
+ xsappname: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}` + '-${space-guid}',
168
+ 'tenant-mode': 'dedicated'
169
+ }
167
170
  }
168
171
  };
169
172
  await this.mta?.addResource(resource);
@@ -172,7 +175,7 @@ class MtaConfig {
172
175
  }
173
176
  async addHtml5Runtime() {
174
177
  const resource = {
175
- name: `${this.prefix?.slice(0, 100)}-html5-repo-runtime`,
178
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-html5-repo-runtime`,
176
179
  type: 'org.cloudfoundry.managed-service',
177
180
  parameters: { 'service-plan': 'app-runtime', service: 'html5-apps-repo' }
178
181
  };
@@ -191,7 +194,7 @@ class MtaConfig {
191
194
  if (resource && !resource.parameters?.['service-name']) {
192
195
  resource.parameters = {
193
196
  ...(resource.parameters ?? {}),
194
- 'service-name': `${this.prefix?.slice(0, 100)}-${serviceName}-service`
197
+ 'service-name': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-${serviceName}-service`
195
198
  };
196
199
  await this.mta?.updateResource(resource);
197
200
  this.resources.set(resourceName, resource);
@@ -218,11 +221,11 @@ class MtaConfig {
218
221
  }
219
222
  async addAppFrontResource() {
220
223
  const resource = {
221
- name: `${this.prefix?.slice(0, 94)}-app-front`,
224
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_SHORT_LENGTH)}-app-front`,
222
225
  type: 'org.cloudfoundry.managed-service',
223
226
  parameters: {
224
227
  service: 'app-front',
225
- 'service-name': `${this.prefix?.slice(0, 96)}-app-front-service`,
228
+ 'service-name': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_SHORTER_LENGTH)}-app-front-service`,
226
229
  'service-plan': 'developer'
227
230
  }
228
231
  };
@@ -231,12 +234,12 @@ class MtaConfig {
231
234
  this.dirty = true;
232
235
  }
233
236
  async addHtml5Host() {
234
- const html5host = `${this.prefix?.slice(0, 100)}-repo-host`; // Need to cater for -key being added too!
237
+ const html5host = `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-repo-host`; // Need to cater for -key being added too!
235
238
  const resource = {
236
239
  name: html5host,
237
240
  type: 'org.cloudfoundry.managed-service',
238
241
  parameters: {
239
- 'service-name': `${this.prefix?.slice(0, 100)}-html5-service`,
242
+ 'service-name': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-html5-service`,
240
243
  'service-plan': 'app-host',
241
244
  service: 'html5-apps-repo'
242
245
  }
@@ -251,7 +254,7 @@ class MtaConfig {
251
254
  * @param isManagedApp - If the destination service is for a managed app
252
255
  */
253
256
  async addDestinationResource(isManagedApp = false) {
254
- const destinationName = `${this.prefix?.slice(0, 100)}-destination-service`;
257
+ const destinationName = `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-destination-service`;
255
258
  const resource = {
256
259
  name: destinationName,
257
260
  type: 'org.cloudfoundry.managed-service',
@@ -331,17 +334,17 @@ class MtaConfig {
331
334
  async addManagedUAAWithSecurity() {
332
335
  this.log?.debug((0, i18n_1.t)('debug.addXsuaaService'));
333
336
  const resource = {
334
- name: `${this.prefix?.slice(0, 100)}-uaa`,
337
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-uaa`,
335
338
  type: 'org.cloudfoundry.managed-service',
336
339
  parameters: {
337
340
  path: './xs-security.json',
338
341
  service: 'xsuaa',
339
- 'service-name': `${this.prefix?.slice(0, 100)}-xsuaa-service`,
342
+ 'service-name': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-xsuaa-service`,
340
343
  'service-plan': 'application',
341
344
  ...(this.modules.has('nodejs') && this.modules.has('com.sap.application.content:appfront')
342
345
  ? {
343
346
  config: {
344
- xsappname: `${this.prefix?.slice(0, 100)}-\${org}-\${space}`,
347
+ xsappname: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-\${org}-\${space}`,
345
348
  'tenant-mode': 'dedicated'
346
349
  }
347
350
  }
@@ -391,7 +394,8 @@ class MtaConfig {
391
394
  this.modules.get('com.sap.application.content:destination'),
392
395
  this.modules.get('approuter.nodejs')
393
396
  ].filter((elem) => elem !== undefined)) {
394
- const destinationName = this.resources.get('destination')?.name ?? `${this.prefix?.slice(0, 100)}-destination-service`;
397
+ const destinationName = this.resources.get('destination')?.name ??
398
+ `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-destination-service`;
395
399
  if (module?.requires?.findIndex((app) => app.name === destinationName) === -1) {
396
400
  if (module.type === 'approuter.nodejs') {
397
401
  module.requires.push({
@@ -436,11 +440,11 @@ class MtaConfig {
436
440
  get cloudServiceName() {
437
441
  let cloudServiceName;
438
442
  this.modules.forEach((contentModule) => {
439
- const moduleDestinations = contentModule.parameters?.content?.instance?.destinations || [];
443
+ const moduleDestinations = contentModule.parameters?.content?.instance?.destinations ?? [];
440
444
  if (contentModule.type === 'com.sap.application.content' && moduleDestinations.length) {
441
445
  // In theory, if there is more than one, it should be same!
442
446
  moduleDestinations.some((destination) => {
443
- cloudServiceName = destination['sap.cloud.service'] || undefined;
447
+ cloudServiceName = destination['sap.cloud.service'];
444
448
  return !!cloudServiceName;
445
449
  });
446
450
  }
@@ -514,7 +518,7 @@ class MtaConfig {
514
518
  }
515
519
  else {
516
520
  contentModule[constants_1.MTABuildParams].requires.push({
517
- name: appName.slice(0, 128),
521
+ name: appName.slice(0, constants_1.MAX_MTA_ID_LENGTH),
518
522
  artifacts: [artifactName],
519
523
  'target-path': `${contentModule[constants_1.MTABuildParams][constants_1.MTABuildResult]}/`.replace(/\/{2,}/g, '/') // Matches two or more consecutive slashes where at least 2 repetitions of /
520
524
  });
@@ -525,7 +529,7 @@ class MtaConfig {
525
529
  // Add application module, if not found already
526
530
  if (!isHTML5AlreadyExisting && !this.apps.get(appName)) {
527
531
  const app = {
528
- name: appName.slice(0, 128),
532
+ name: appName.slice(0, constants_1.MAX_MTA_ID_LENGTH),
529
533
  type: 'html5',
530
534
  path: appPath,
531
535
  'build-parameters': constants_1.HTMLAppBuildParams
@@ -566,7 +570,7 @@ class MtaConfig {
566
570
  async addConnectivityResource() {
567
571
  const serviceType = 'connectivity';
568
572
  const resourceType = types_1.CloudFoundryServiceType.Managed;
569
- const resourceName = `${this.prefix?.slice(0, 100)}-connectivity`;
573
+ const resourceName = `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-connectivity`;
570
574
  const router = this.modules.get('approuter.nodejs');
571
575
  if (router) {
572
576
  if (router.requires?.findIndex((resource) => resource.name === resourceName) === -1) {
@@ -647,7 +651,7 @@ class MtaConfig {
647
651
  * @returns {Promise<void>} A promise that resolves when the change request has been processed.
648
652
  */
649
653
  async addAbapService(serviceName, btpService) {
650
- const newResourceName = `${this.prefix?.slice(0, 24)}-abap-${serviceName.slice(0, 20)}`;
654
+ const newResourceName = `${this.prefix?.slice(0, constants_1.MAX_ABAP_SERVICE_PREFIX_LENGTH)}-abap-${serviceName.slice(0, constants_1.MAX_ABAP_SERVICE_NAME_LENGTH)}`;
651
655
  const router = this.modules.get('approuter.nodejs');
652
656
  if (router) {
653
657
  if (router.requires?.findIndex((resource) => resource.name === newResourceName) === -1) {
@@ -709,7 +713,7 @@ class MtaConfig {
709
713
  const destinationName = this.resources.get('destination')?.name;
710
714
  if (destinationName && xsuaaName && appRuntimeName) {
711
715
  const router = {
712
- name: `${this.prefix?.slice(0, 100)}-router`,
716
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-router`,
713
717
  type: 'approuter.nodejs',
714
718
  path: fromServerGenerator ? `${constants_1.RouterModule}` : `${constants_1.CloudFoundry}/${constants_1.RouterModule}`,
715
719
  parameters: {
@@ -763,7 +767,7 @@ class MtaConfig {
763
767
  let destinationServiceName = this.resources.get('destination')?.name;
764
768
  if (!destinationServiceName) {
765
769
  this.log?.info((0, i18n_1.t)('info.existingDestinationNotFound'));
766
- destinationServiceName = `${this.prefix?.slice(0, 100)}-destination-service`;
770
+ destinationServiceName = `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-destination-service`;
767
771
  }
768
772
  const appMtaId = this.mtaId;
769
773
  const mtaExtFilePath = (0, node_path_1.join)(this.mta.mtaDirPath, project_access_1.FileName.MtaExtYaml);
@@ -922,7 +926,7 @@ class MtaConfig {
922
926
  const appHostName = this.resources.get(constants_1.ManagedAppFront)?.name;
923
927
  if (appHostName) {
924
928
  const appContentModule = {
925
- name: `${this.prefix?.slice(0, 100)}-app-content`,
929
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-app-content`,
926
930
  type: 'com.sap.application.content',
927
931
  path: '.',
928
932
  requires: [
@@ -981,7 +985,7 @@ class MtaConfig {
981
985
  const appHostServiceName = this.getServiceInstanceName(constants_1.HTML5RepoHost);
982
986
  const managedXSUAAServiceName = this.getServiceInstanceName(constants_1.ManagedXSUAA);
983
987
  const router = {
984
- name: `${this.prefix?.slice(0, 100)}-destination-content`,
988
+ name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}-destination-content`,
985
989
  type: 'com.sap.application.content',
986
990
  requires: [
987
991
  {
@@ -1012,17 +1016,17 @@ class MtaConfig {
1012
1016
  instance: {
1013
1017
  destinations: [
1014
1018
  {
1015
- Name: `${this.prefix?.slice(0, 100)}_html_repo_host`,
1019
+ Name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}_html_repo_host`,
1016
1020
  ServiceInstanceName: appHostServiceName,
1017
1021
  ServiceKeyName: `${appHostName}-key`,
1018
- 'sap.cloud.service': `${this.prefix?.slice(0, 100)}`
1022
+ 'sap.cloud.service': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}`
1019
1023
  },
1020
1024
  {
1021
1025
  Authentication: 'OAuth2UserTokenExchange',
1022
- Name: `${this.prefix?.slice(0, 100)}_uaa`,
1026
+ Name: `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}_uaa`,
1023
1027
  ServiceInstanceName: managedXSUAAServiceName,
1024
1028
  ServiceKeyName: `${managedXSUAAName}-key`,
1025
- 'sap.cloud.service': `${this.prefix?.slice(0, 100)}`
1029
+ 'sap.cloud.service': `${this.prefix?.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH)}`
1026
1030
  }
1027
1031
  ],
1028
1032
  'existing_destinations_policy': 'update'
package/dist/utils.d.ts CHANGED
@@ -3,13 +3,13 @@ import { type Authentication, type Destinations } from '@sap-ux/btp-utils';
3
3
  import { type Manifest } from '@sap-ux/project-access';
4
4
  import { type MTABaseConfig, type CFBaseConfig, type CFAppConfig } from './types';
5
5
  /**
6
- * Read manifest file for processing.
6
+ * Read manifest file for processing.
7
7
  *
8
8
  * @param manifestPath Path to the manifest file
9
9
  * @param fs reference to a mem-fs editor
10
10
  * @returns Manifest object
11
11
  */
12
- export declare function readManifest(manifestPath: string, fs: Editor): Promise<Manifest>;
12
+ export declare function readManifest(manifestPath: string, fs: Editor): Manifest;
13
13
  /**
14
14
  * Locates template files relative to the dist folder.
15
15
  * This helps to locate templates when this module is bundled and the dir structure is flattened, maintaining the relative paths.
@@ -19,10 +19,11 @@ export declare function readManifest(manifestPath: string, fs: Editor): Promise<
19
19
  */
20
20
  export declare function getTemplatePath(relativeTemplatePath: string): string;
21
21
  /**
22
- * Convert an app name to an MTA ID that is suitable for CF deployment.
22
+ * Convert an app name to an MTA ID that is suitable for CF deployment.
23
+ * Removes special characters that are not allowed in MTA module names.
23
24
  *
24
25
  * @param id Name of the app, like `sap.ux.app`
25
- * @returns Name that's acceptable in an mta.yaml
26
+ * @returns Name that's acceptable in an mta.yaml (special characters removed)
26
27
  */
27
28
  export declare function toMtaModuleName(id: string): string;
28
29
  /**
@@ -143,7 +144,7 @@ export declare function alignCdsVersions(rootPath: string, fs: Editor): Promise<
143
144
  * @throws {Error} Throws an error with the provided error message concatenated with the original error if execution fails
144
145
  * @example
145
146
  * // Execute npm install in the project directory
146
- * await runCommand('/path/to/project', 'npm', ['install'], 'Failed to install dependencies:');
147
+ * await runCommand('/path/to/project', 'npm', ['install'], 'Failed to install dependencies');
147
148
  */
148
149
  export declare function runCommand(cwd: string, cmd: string, args: string[], errorMsg: string): Promise<void>;
149
150
  /**
package/dist/utils.js CHANGED
@@ -26,13 +26,13 @@ const project_access_1 = require("@sap-ux/project-access");
26
26
  const constants_1 = require("./constants");
27
27
  let cachedDestinationsList = {};
28
28
  /**
29
- * Read manifest file for processing.
29
+ * Read manifest file for processing.
30
30
  *
31
31
  * @param manifestPath Path to the manifest file
32
32
  * @param fs reference to a mem-fs editor
33
33
  * @returns Manifest object
34
34
  */
35
- async function readManifest(manifestPath, fs) {
35
+ function readManifest(manifestPath, fs) {
36
36
  return fs.readJSON(manifestPath);
37
37
  }
38
38
  /**
@@ -46,13 +46,17 @@ function getTemplatePath(relativeTemplatePath) {
46
46
  return (0, node_path_1.join)(__dirname, '../templates', relativeTemplatePath);
47
47
  }
48
48
  /**
49
- * Convert an app name to an MTA ID that is suitable for CF deployment.
49
+ * Convert an app name to an MTA ID that is suitable for CF deployment.
50
+ * Removes special characters that are not allowed in MTA module names.
50
51
  *
51
52
  * @param id Name of the app, like `sap.ux.app`
52
- * @returns Name that's acceptable in an mta.yaml
53
+ * @returns Name that's acceptable in an mta.yaml (special characters removed)
53
54
  */
54
55
  function toMtaModuleName(id) {
55
- return id.replace(/[`~!@#$%^&*£()|+=?;:'",.<>]/gi, '');
56
+ // Remove special characters not allowed in MTA module names
57
+ // Keep alphanumeric, underscore, hyphen, and dot
58
+ // Using replaceAll for global replacement (Sonar S7781)
59
+ return id.replaceAll(/[`~!@#$%^&*£()|+=?;:'",.<>]/gi, '');
56
60
  }
57
61
  /**
58
62
  * Return a consistent file path across different platforms.
@@ -117,7 +121,7 @@ function validateVersion(mtaVersion) {
117
121
  */
118
122
  function addXSSecurityConfig({ mtaPath, mtaId }, fs, addTenant = true) {
119
123
  fs.copyTpl(getTemplatePath(`common/${project_access_1.FileName.XSSecurityJson}`), (0, node_path_1.join)(mtaPath, project_access_1.FileName.XSSecurityJson), {
120
- id: mtaId.slice(0, 100),
124
+ id: mtaId.slice(0, constants_1.MAX_MTA_PREFIX_LENGTH),
121
125
  addTenant
122
126
  });
123
127
  }
@@ -180,7 +184,7 @@ async function generateSupportingConfig(config, fs, addTenant = true) {
180
184
  */
181
185
  function setMtaDefaults(config) {
182
186
  config.mtaPath = config.mtaPath.replace(/\/$/, '');
183
- config.addConnectivityService ||= false;
187
+ config.addConnectivityService ??= false;
184
188
  config.mtaId = toMtaModuleName(config.mtaId);
185
189
  }
186
190
  /**
@@ -260,15 +264,16 @@ async function alignCdsVersions(rootPath, fs) {
260
264
  * @throws {Error} Throws an error with the provided error message concatenated with the original error if execution fails
261
265
  * @example
262
266
  * // Execute npm install in the project directory
263
- * await runCommand('/path/to/project', 'npm', ['install'], 'Failed to install dependencies:');
267
+ * await runCommand('/path/to/project', 'npm', ['install'], 'Failed to install dependencies');
264
268
  */
265
269
  async function runCommand(cwd, cmd, args, errorMsg) {
266
270
  const commandRunner = new nodejs_utils_1.CommandRunner();
267
271
  try {
268
272
  await commandRunner.run(cmd, args, { cwd });
269
273
  }
270
- catch (e) {
271
- throw new Error(`${errorMsg} ${e.message ?? e}`);
274
+ catch (error) {
275
+ const errorMessage = error instanceof Error ? error.message : String(error);
276
+ throw new Error(`${errorMsg}: ${errorMessage}`);
272
277
  }
273
278
  }
274
279
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sap-ux/cf-deploy-config-writer",
3
3
  "description": "Add or amend Cloud Foundry and ABAP deployment configuration for SAP projects",
4
- "version": "0.3.63",
4
+ "version": "0.3.65",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -29,7 +29,7 @@
29
29
  "mem-fs": "2.1.0",
30
30
  "mem-fs-editor": "9.4.0",
31
31
  "hasbin": "1.2.3",
32
- "@sap-ux/project-access": "1.35.1",
32
+ "@sap-ux/project-access": "1.35.2",
33
33
  "@sap-ux/yaml": "0.17.4",
34
34
  "@sap-ux/btp-utils": "1.1.8",
35
35
  "@sap-ux/logger": "0.8.1",