@ui5/task-adaptation 1.5.0 → 1.5.2-rc.0

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/bundle.js CHANGED
@@ -4955,30 +4955,31 @@ var FilterOperator = {
4955
4955
  EndsWith: "EndsWith",
4956
4956
  NotEndsWith: "NotEndsWith",
4957
4957
  All: "All",
4958
- Any: "Any"
4958
+ NotAll: "NotAll",
4959
+ Any: "Any",
4960
+ NotAny: "NotAny"
4959
4961
  };
4960
4962
 
4961
- class config {
4962
- config = new Map();
4963
- static get Type() {
4964
- return {
4965
- String: "string"
4966
- }
4967
- }
4968
- static get({ name }) {
4969
- return name === "sapUiLogLevel" ? "Error" : undefined;
4970
- }
4971
- static getWritableInstance() {
4972
- return {
4973
- get(obj) {
4974
- config.get(obj.name);
4975
- },
4976
- set(name, obj) {
4977
- config.set(name, obj);
4978
- }
4979
- }
4963
+ const _Configuration = {
4964
+ _: {}
4965
+ };
4966
+ const internalConfig = new Map();
4967
+ _Configuration.getWritableInstance = () => {
4968
+ return {
4969
+ get(obj) {
4970
+ internalConfig.get(obj.name);
4971
+ },
4972
+ set(name, obj) {
4973
+ internalConfig.set(name, obj);
4980
4974
  }
4981
- }
4975
+ };
4976
+ };
4977
+ function attachInvalidated() {}
4978
+ _Configuration._.attachInvalidated = attachInvalidated;
4979
+ const origInvalidate = _Configuration._.invalidate;
4980
+ _Configuration._.invalidate = () => {
4981
+ origInvalidate();
4982
+ };
4982
4983
 
4983
4984
  class Eventing {
4984
4985
  #mEventRegistry = {};
@@ -5210,6 +5211,11 @@ class LanguageTag {
5210
5211
  */
5211
5212
  privateUseSubtags;
5212
5213
 
5214
+ /**
5215
+ * @private
5216
+ */
5217
+ #tagAsString;
5218
+
5213
5219
  constructor(sLanguageTag) {
5214
5220
  var aResult = rLanguageTag.exec(sLanguageTag.replace(/_/g, "-"));
5215
5221
  // If the given language tag string cannot be parsed by the regular expression above,
@@ -5241,16 +5247,17 @@ class LanguageTag {
5241
5247
  if ( this.region ) {
5242
5248
  this.region = this.region.toUpperCase();
5243
5249
  }
5244
- Object.freeze(this);
5245
- }
5246
- toString() {
5247
- return this.#join(
5250
+ this.#tagAsString = this.#join(
5248
5251
  this.language,
5249
5252
  this.script,
5250
5253
  this.region,
5251
5254
  this.variant,
5252
5255
  this.extension,
5253
5256
  this.privateUse);
5257
+ Object.freeze(this);
5258
+ }
5259
+ toString() {
5260
+ return this.#tagAsString;
5254
5261
  }
5255
5262
  #join() {
5256
5263
  return Array.prototype.filter.call(arguments, Boolean).join("-");
@@ -5550,11 +5557,15 @@ TimezoneUtils._clearLocalTimezoneCache = function () {
5550
5557
  sLocalTimezone = "";
5551
5558
  };
5552
5559
 
5553
- const oWritableConfig = config.getWritableInstance();
5560
+ const oWritableConfig = _Configuration.getWritableInstance();
5554
5561
  let sLanguageSetByApi;
5555
5562
  const oEventing = new Eventing();
5563
+ let mCache = Object.create(null);
5556
5564
  let mChanges;
5557
5565
  let bLanguageWarningLogged = false;
5566
+ _Configuration._.attachInvalidated(() => {
5567
+ mCache = Object.create(null);
5568
+ });
5558
5569
  const _mPreferredCalendar = {
5559
5570
  "ar-SA": CalendarType.Islamic,
5560
5571
  "fa": CalendarType.Persian,
@@ -5615,9 +5626,11 @@ function createLanguageTag(sLanguage) {
5615
5626
  }
5616
5627
  return oLanguageTag;
5617
5628
  }
5618
- function detectLanguage() {
5619
- return globalThis.navigator ? globalThis.navigator.languages && globalThis.navigator.languages[0] || globalThis.navigator.language || "en" : new Intl.Collator().resolvedOptions().locale || "en";
5620
- }
5629
+ const detectLanguage = globalThis.navigator ? function () {
5630
+ return globalThis.navigator.languages?.[0] || globalThis.navigator.language || "en";
5631
+ } : function () {
5632
+ return new Intl.Collator().resolvedOptions().locale || "en";
5633
+ };
5621
5634
  function check(bCondition, sMessage) {
5622
5635
  if (!bCondition) {
5623
5636
  throw new TypeError(sMessage);
@@ -5643,29 +5656,32 @@ const Localization = {
5643
5656
  getActiveTerminologies: function () {
5644
5657
  return oWritableConfig.get({
5645
5658
  name: "sapUiActiveTerminologies",
5646
- type: config.Type.StringArray,
5659
+ type: _Configuration.Type.StringArray,
5647
5660
  defaultValue: undefined,
5648
5661
  external: true
5649
5662
  });
5650
5663
  },
5651
5664
  getLanguage: function () {
5652
5665
  let oLanguageTag, sDerivedLanguage;
5666
+ if (mCache.language) {
5667
+ return mCache.language;
5668
+ }
5653
5669
  if (sLanguageSetByApi) {
5654
5670
  return sLanguageSetByApi;
5655
5671
  }
5656
5672
  const sLanguage = oWritableConfig.get({
5657
5673
  name: "sapUiLanguage",
5658
- type: config.Type.String,
5674
+ type: _Configuration.Type.String,
5659
5675
  external: true
5660
5676
  });
5661
5677
  const sSapLocale = oWritableConfig.get({
5662
5678
  name: "sapLocale",
5663
- type: config.Type.String,
5679
+ type: _Configuration.Type.String,
5664
5680
  external: true
5665
5681
  });
5666
5682
  const sSapLanguage = oWritableConfig.get({
5667
5683
  name: "sapLanguage",
5668
- type: config.Type.String,
5684
+ type: _Configuration.Type.String,
5669
5685
  external: true
5670
5686
  });
5671
5687
  if (sSapLocale) {
@@ -5680,14 +5696,15 @@ const Localization = {
5680
5696
  }
5681
5697
  if (!oLanguageTag) {
5682
5698
  if (sLanguage) {
5683
- oLanguageTag = createLanguageTag(sLanguage);
5699
+ createLanguageTag(sLanguage);
5684
5700
  sDerivedLanguage = sLanguage;
5685
5701
  } else {
5686
5702
  sDerivedLanguage = detectLanguage();
5687
- oLanguageTag = createLanguageTag(sLanguage);
5703
+ createLanguageTag(sLanguage);
5688
5704
  }
5689
5705
  }
5690
- return sDerivedLanguage;
5706
+ mCache.language = sDerivedLanguage;
5707
+ return mCache.language;
5691
5708
  },
5692
5709
  getModernLanguage: function (sLanguage) {
5693
5710
  return M_ISO639_OLD_TO_NEW[sLanguage] || sLanguage;
@@ -5699,7 +5716,7 @@ const Localization = {
5699
5716
  sSAPLogonLanguage = sSAPLogonLanguage || "";
5700
5717
  if (oLanguageTag.toString() != Localization.getLanguageTag().toString() || sSAPLogonLanguage !== oWritableConfig.get({
5701
5718
  name: "sapLanguage",
5702
- type: config.Type.String,
5719
+ type: _Configuration.Type.String,
5703
5720
  external: true
5704
5721
  })) {
5705
5722
  oWritableConfig.set("sapLanguage", sSAPLogonLanguage);
@@ -5714,20 +5731,24 @@ const Localization = {
5714
5731
  }
5715
5732
  },
5716
5733
  getTimezone: function () {
5734
+ if (mCache.timezone) {
5735
+ return mCache.timezone;
5736
+ }
5717
5737
  let sTimezone = oWritableConfig.get({
5718
5738
  name: "sapTimezone",
5719
- type: config.Type.String,
5739
+ type: _Configuration.Type.String,
5720
5740
  external: true,
5721
5741
  defaultValue: oWritableConfig.get({
5722
5742
  name: "sapUiTimezone",
5723
- type: config.Type.String,
5743
+ type: _Configuration.Type.String,
5724
5744
  external: true
5725
5745
  })
5726
5746
  });
5727
5747
  if (!sTimezone || !checkTimezone(sTimezone)) {
5728
5748
  sTimezone = TimezoneUtils.getLocalTimezone();
5729
5749
  }
5730
- return sTimezone;
5750
+ mCache.timezone = sTimezone;
5751
+ return mCache.timezone;
5731
5752
  },
5732
5753
  setTimezone: function (sTimezone) {
5733
5754
  check(sTimezone == null || typeof sTimezone === "string", "Localization.setTimezone: sTimezone must be null or be a string");
@@ -5741,6 +5762,9 @@ const Localization = {
5741
5762
  }
5742
5763
  },
5743
5764
  getLanguageTag: function () {
5765
+ if (mCache.languageTag) {
5766
+ return mCache.languageTag;
5767
+ }
5744
5768
  const oLanguageTag = new LanguageTag(Localization.getLanguage());
5745
5769
  const sLanguage = Localization.getModernLanguage(oLanguageTag.language);
5746
5770
  const sScript = oLanguageTag.script;
@@ -5750,22 +5774,27 @@ const Localization = {
5750
5774
  } else {
5751
5775
  sLanguageTag = sLanguageTag.replace(oLanguageTag.language, sLanguage);
5752
5776
  }
5753
- return new LanguageTag(sLanguageTag);
5777
+ mCache.languageTag = new LanguageTag(sLanguageTag);
5778
+ return mCache.languageTag;
5754
5779
  },
5755
5780
  getRTL: function () {
5756
- return oWritableConfig.get({
5781
+ if (mCache.rtl) {
5782
+ return mCache.rtl;
5783
+ }
5784
+ mCache.rtl = oWritableConfig.get({
5757
5785
  name: "sapRtl",
5758
- type: config.Type.Boolean,
5786
+ type: _Configuration.Type.Boolean,
5759
5787
  external: true,
5760
5788
  defaultValue: oWritableConfig.get({
5761
5789
  name: "sapUiRtl",
5762
- type: config.Type.Boolean,
5790
+ type: _Configuration.Type.Boolean,
5763
5791
  defaultValue: function () {
5764
5792
  return impliesRTL(Localization.getLanguageTag());
5765
5793
  },
5766
5794
  external: true
5767
5795
  })
5768
5796
  });
5797
+ return mCache.rtl;
5769
5798
  },
5770
5799
  setRTL: function (bRTL) {
5771
5800
  check(bRTL === null || typeof bRTL === "boolean", "bRTL must be null or a boolean");
@@ -5794,7 +5823,7 @@ const Localization = {
5794
5823
  let oLanguageTag;
5795
5824
  const sLanguage = oWritableConfig.get({
5796
5825
  name: "sapLanguage",
5797
- type: config.Type.String,
5826
+ type: _Configuration.Type.String,
5798
5827
  external: true
5799
5828
  }).toUpperCase();
5800
5829
  try {
@@ -5813,9 +5842,9 @@ const Localization = {
5813
5842
  return _coreI18nLocales;
5814
5843
  },
5815
5844
  getSupportedLanguages: function () {
5816
- let aLangs = config.get({
5845
+ let aLangs = _Configuration.get({
5817
5846
  name: "sapUiXxSupportedLanguages",
5818
- type: config.Type.StringArray,
5847
+ type: _Configuration.Type.StringArray,
5819
5848
  external: true
5820
5849
  });
5821
5850
  if (aLangs.length === 0 || aLangs.length === 1 && aLangs[0] === "*") {
@@ -5870,8 +5899,8 @@ var Filter = BaseObject.extend("sap.ui.model.Filter", {
5870
5899
  }
5871
5900
  this.oValue1 = vValue1;
5872
5901
  this.oValue2 = vValue2;
5873
- if (this.sOperator === FilterOperator.Any || this.sOperator === FilterOperator.All) {
5874
- throw new Error("The filter operators 'Any' and 'All' are only supported with the parameter object notation.");
5902
+ if (this.sOperator === FilterOperator.Any || this.sOperator === FilterOperator.All || this.sOperator === FilterOperator.NotAny || this.sOperator === FilterOperator.NotAll) {
5903
+ throw new Error("The filter operators 'Any', 'All', 'NotAny', and 'NotAll' are only supported with " + "the parameter object notation.");
5875
5904
  }
5876
5905
  }
5877
5906
  if (this.aFilters?.includes(Filter.NONE)) {
@@ -5879,13 +5908,13 @@ var Filter = BaseObject.extend("sap.ui.model.Filter", {
5879
5908
  } else if (this.oCondition && this.oCondition === Filter.NONE) {
5880
5909
  throw new Error("Filter.NONE not allowed as condition");
5881
5910
  }
5882
- if (this.sOperator === FilterOperator.Any) {
5911
+ if (this.sOperator === FilterOperator.Any || this.sOperator === FilterOperator.NotAny) {
5883
5912
  if (this.sVariable && this.oCondition) {
5884
5913
  this._checkLambdaArgumentTypes();
5885
5914
  } else if (!this.sVariable && !this.oCondition) ; else {
5886
- throw new Error("When using the filter operator 'Any', a lambda variable and a condition have to be given or neither.");
5915
+ throw new Error("When using the filter operator 'Any' or 'NotAny', you need to provide " + "both a lambda variable and a condition, or neither.");
5887
5916
  }
5888
- } else if (this.sOperator === FilterOperator.All) {
5917
+ } else if (this.sOperator === FilterOperator.All || this.sOperator === FilterOperator.NotAll) {
5889
5918
  this._checkLambdaArgumentTypes();
5890
5919
  } else if (Array.isArray(this.aFilters) && !this.sPath && !this.sOperator && !this.oValue1 && !this.oValue2) {
5891
5920
  this._bMultiFilter = true;
@@ -5912,10 +5941,10 @@ Filter.checkFilterNone = function (vFilter) {
5912
5941
  };
5913
5942
  Filter.prototype._checkLambdaArgumentTypes = function () {
5914
5943
  if (!this.sVariable || typeof this.sVariable !== "string") {
5915
- throw new Error("When using the filter operators 'Any' or 'All', a string has to be given as argument 'variable'.");
5944
+ throw new Error("When using the filter operators 'Any', 'All', 'NotAny', or 'NotAll', a string has to be " + "given as the 'variable' argument.");
5916
5945
  }
5917
5946
  if (!isFilter(this.oCondition)) {
5918
- throw new Error("When using the filter operator 'Any' or 'All', a valid instance of sap.ui.model.Filter has to be given as argument 'condition'.");
5947
+ throw new Error("When using the filter operator 'Any', 'All', 'NotAny', or 'NotAll', a valid instance of " + "sap.ui.model.Filter has to be given as the 'condition' argument.");
5919
5948
  }
5920
5949
  };
5921
5950
  function isFilter(v) {
@@ -5955,7 +5984,7 @@ var Func = {
5955
5984
  EndsWith: "endswith"
5956
5985
  };
5957
5986
  Filter.prototype.getAST = function (bIncludeOrigin) {
5958
- var oResult, sOp, sOrigOp, oRef, oValue, oFromValue, oToValue, oVariable, oCondition;
5987
+ var oResult, sOp, sOrigOp, oRef, oValue, oFromValue, oToValue;
5959
5988
  function logical(sOp, oLeft, oRight) {
5960
5989
  return {
5961
5990
  type: Type.Logical,
@@ -6074,10 +6103,19 @@ Filter.prototype.getAST = function (bIncludeOrigin) {
6074
6103
  break;
6075
6104
  case FilterOperator.Any:
6076
6105
  case FilterOperator.All:
6077
- oVariable = variable(this.sVariable);
6078
- oCondition = this.oCondition.getAST(bIncludeOrigin);
6079
- oResult = lambda(sOp, oRef, oVariable, oCondition);
6080
- break;
6106
+ case FilterOperator.NotAny:
6107
+ case FilterOperator.NotAll:
6108
+ {
6109
+ let sOpLambda = sOp;
6110
+ if (sOp === FilterOperator.NotAny) {
6111
+ sOpLambda = FilterOperator.Any;
6112
+ } else if (sOp === FilterOperator.NotAll) {
6113
+ sOpLambda = FilterOperator.All;
6114
+ }
6115
+ const oLambda = lambda(sOpLambda, oRef, variable(this.sVariable), this.oCondition.getAST(bIncludeOrigin));
6116
+ oResult = sOp === FilterOperator.NotAny || sOp === FilterOperator.NotAll ? unary(Op.Not, oLambda) : oLambda;
6117
+ break;
6118
+ }
6081
6119
  default:
6082
6120
  throw new Error("Unknown operator: " + sOp);
6083
6121
  }
package/dist/index.js CHANGED
@@ -17,34 +17,57 @@ export default ({ workspace, options, taskUtil }) => {
17
17
  const processor = determineProcessor(options.configuration);
18
18
  const adaptationProject = await AppVariant.fromWorkspace(workspace, options.projectNamespace);
19
19
  const appVariantIdHierarchy = await processor.getAppVariantIdHierarchy(adaptationProject.reference);
20
+ if (appVariantIdHierarchy.length === 0) {
21
+ throw new Error(`No app variant found for reference ${adaptationProject.reference}`);
22
+ }
20
23
  // appVariantIdHierarchy contains original application on bottom and the
21
24
  // latest app variant on top. We reverse the list to process original
22
25
  // application first and then app variants in chronological order.
23
26
  const reversedHierarchy = appVariantIdHierarchy.toReversed();
24
27
  const fetchFilesPromises = reversedHierarchy.map(({ repoName, cachebusterToken }) => processor.fetch(repoName, cachebusterToken));
25
28
  fetchFilesPromises.push(Promise.resolve(adaptationProject.files));
29
+ const appVariants = new Array();
26
30
  const adapt = async (baseAppFiles, appVariantFiles) => {
27
- const baseApp = BaseApp.fromFiles(baseAppFiles);
28
- const appVariant = AppVariant.fromFiles(appVariantFiles);
31
+ let baseApp = BaseApp.fromFiles(baseAppFiles);
32
+ let appVariant = AppVariant.fromFiles(appVariantFiles);
33
+ // If the app variant is the same as the adaptation project, we use the
34
+ // adaptation project because it contains resources that should be updated.
35
+ if (appVariant.id === adaptationProject.id) {
36
+ appVariant = adaptationProject;
37
+ }
38
+ appVariants.push(appVariant);
29
39
  const adaptedFiles = await baseApp.adapt(appVariant, processor);
30
40
  return I18nMerger.merge(adaptedFiles, baseApp.i18nPath, appVariant);
31
41
  };
32
- let files = await fetchFilesPromises.reduce(async (previousFiles, currentFiles) => {
33
- return adapt(await previousFiles, await currentFiles);
34
- }, fetchFilesPromises.shift());
35
- if (files) {
36
- const references = appVariantIdHierarchy.map(item => item.appVariantId);
37
- files = FilesUtil.filter(files);
38
- files = FilesUtil.rename(files, references, adaptationProject.id);
39
- adaptationProject.omitDeletedResources(files, options.projectNamespace, taskUtil);
40
- const writePromises = new Array();
41
- files.forEach((content, filename) => {
42
- const resource = ResourceUtil.createResource(filename, options.projectNamespace, content);
43
- writePromises.push(workspace.write(resource));
44
- });
45
- await Promise.all(writePromises);
46
- }
42
+ let files = await fetchFilesPromises.reduce(async (previousFiles, currentFiles) => adapt(await previousFiles, await currentFiles), fetchFilesPromises.shift());
43
+ const references = getReferences(appVariants, adaptationProject.id);
44
+ files = FilesUtil.filter(files);
45
+ files = FilesUtil.rename(files, references);
46
+ adaptationProject.omitDeletedResources(files, options.projectNamespace, taskUtil);
47
+ const writePromises = new Array();
48
+ files.forEach((content, filename) => {
49
+ const resource = ResourceUtil.createResource(filename, options.projectNamespace, content);
50
+ writePromises.push(workspace.write(resource));
51
+ });
52
+ await Promise.all(writePromises);
47
53
  }
48
54
  return process(workspace, taskUtil);
55
+ /**
56
+ * 4p. Reference map contains searchTerm as key and replacement as value. Base
57
+ * id and app variant ids are renamed to adaptation project id.
58
+ */
59
+ function getReferences(appVariants, adaptationProjectId) {
60
+ const references = new Map();
61
+ appVariants.forEach((variant, i) => {
62
+ if (i === 0) {
63
+ references.set(variant.reference, adaptationProjectId);
64
+ }
65
+ if (variant.id !== adaptationProjectId) {
66
+ references.set(variant.id, adaptationProjectId);
67
+ }
68
+ variant.getRenamingForMovedFiles().forEach((value, key) => references.set(key, value));
69
+ });
70
+ return references;
71
+ }
49
72
  };
50
73
  //# sourceMappingURL=index.js.map
@@ -14,6 +14,7 @@ export interface IConfiguration {
14
14
  enableBetaFeatures?: boolean;
15
15
  writeTempFiles?: any;
16
16
  target?: AbapTarget & IAbapTargetMeta;
17
+ serviceInstanceName?: string;
17
18
  }
18
19
  export interface IAbapTargetMeta {
19
20
  name?: string;
@@ -11,7 +11,7 @@ export default class AbapProcessor implements IProcessor {
11
11
  getAppVariantIdHierarchy(appId: string): Promise<IAppVariantIdHierarchyItem[]>;
12
12
  fetch(repoName: string, _cachebusterToken: string): Promise<Map<string, string>>;
13
13
  validateConfiguration(): void;
14
- updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>, appVariantId: string): Promise<void>;
14
+ updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>, appVariantId: string, prefix: string): Promise<void>;
15
15
  getConfigurationType(): string;
16
16
  createAppVariantHierarchyItem(appVariantId: string, version: string): {
17
17
  appVariantId: string;
@@ -27,9 +27,9 @@ export default class AbapProcessor {
27
27
  const properties = ["appName"];
28
28
  validateObject(this.configuration, properties, "should be specified in ui5.yaml configuration");
29
29
  }
30
- async updateLandscapeSpecificContent(baseAppManifest, baseAppFiles, appVariantId) {
30
+ async updateLandscapeSpecificContent(baseAppManifest, baseAppFiles, appVariantId, prefix) {
31
31
  const languages = Language.create(this.configuration.languages);
32
- const files = await this.annotationManager.process(baseAppManifest, languages, appVariantId);
32
+ const files = await this.annotationManager.process(baseAppManifest, languages, appVariantId, prefix);
33
33
  if (baseAppFiles) {
34
34
  files.forEach((value, key) => baseAppFiles.set(key, value));
35
35
  }
@@ -7,7 +7,9 @@ export default class CFProcessor implements IProcessor {
7
7
  getAppVariantIdHierarchy(appId: string): Promise<IAppInfo[]>;
8
8
  fetch(_repoName: string, _cachebusterToken: string): Promise<Map<string, string>>;
9
9
  validateConfiguration(): void;
10
- updateLandscapeSpecificContent(renamedBaseAppManifest: any): Promise<void>;
10
+ updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>): Promise<void>;
11
+ private updateXsAppJson;
12
+ private enhanceRoutesWithEndpointAndService;
11
13
  private updateCloudPlatform;
12
14
  getConfigurationType(): string;
13
15
  createAppVariantHierarchyItem(appVariantId: string, version: string): {
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import HTML5RepoManager from "../repositories/html5RepoManager.js";
8
8
  import { cached } from "../cache/cacheHolder.js";
9
9
  import { validateObject } from "../util/commonUtil.js";
10
+ import CFUtil from "../util/cfUtil.js";
10
11
  export default class CFProcessor {
11
12
  configuration;
12
13
  constructor(configuration) {
@@ -26,8 +27,56 @@ export default class CFProcessor {
26
27
  validateConfiguration() {
27
28
  validateObject(this.configuration, ["appHostId", "appName", "appVersion"], "should be specified in ui5.yaml configuration");
28
29
  }
29
- async updateLandscapeSpecificContent(renamedBaseAppManifest) {
30
- this.updateCloudPlatform(renamedBaseAppManifest);
30
+ async updateLandscapeSpecificContent(baseAppManifest, baseAppFiles) {
31
+ this.updateCloudPlatform(baseAppManifest);
32
+ await this.updateXsAppJson(baseAppFiles);
33
+ }
34
+ async updateXsAppJson(baseAppFiles) {
35
+ const xsAppJsonContent = baseAppFiles.get("xs-app.json");
36
+ if (!xsAppJsonContent) {
37
+ return;
38
+ }
39
+ const { serviceInstanceName, space } = this.configuration;
40
+ if (!serviceInstanceName) {
41
+ throw new Error(`Service instance name must be specified in ui5.yaml configuration for app '${this.configuration.appName}'`);
42
+ }
43
+ let serviceCredentials;
44
+ try {
45
+ // Get valid service keys with proper endpoints structure
46
+ serviceCredentials = await CFUtil.getOrCreateServiceKeyWithEndpoints(serviceInstanceName, space);
47
+ }
48
+ catch (error) {
49
+ throw new Error(`Failed to get valid service keys for app '${this.configuration.appName}': ${error.message}`);
50
+ }
51
+ const xsAppJson = JSON.parse(xsAppJsonContent);
52
+ xsAppJson.routes = this.enhanceRoutesWithEndpointAndService(serviceCredentials, xsAppJson.routes);
53
+ baseAppFiles.set("xs-app.json", JSON.stringify(xsAppJson, null, 2));
54
+ }
55
+ enhanceRoutesWithEndpointAndService(serviceCredentials, baseRoutes) {
56
+ const endpoints = serviceCredentials.endpoints;
57
+ // Map destinations to endpoint names
58
+ const destinationToEndpoint = Object.entries(endpoints).reduce((acc, [endpointName, obj]) => {
59
+ if (obj.destination) {
60
+ acc[obj.destination] = endpointName;
61
+ }
62
+ return acc;
63
+ }, {});
64
+ return baseRoutes.map((route) => {
65
+ const endpointName = destinationToEndpoint[route.destination];
66
+ if (endpointName) {
67
+ // There is a matching endpoint: remove destination and add endpoint/service
68
+ const { destination: _destination, ...rest } = route;
69
+ return {
70
+ ...rest,
71
+ endpoint: endpointName,
72
+ service: serviceCredentials["sap.cloud.service"],
73
+ };
74
+ }
75
+ else {
76
+ // No match: return route unchanged
77
+ return route;
78
+ }
79
+ });
31
80
  }
32
81
  updateCloudPlatform(baseAppManifest) {
33
82
  const sapCloudService = baseAppManifest["sap.cloud"]?.service;
@@ -4,6 +4,6 @@ export default interface IProcessor {
4
4
  getAppVariantIdHierarchy(appId: string): Promise<IAppVariantIdHierarchyItem[]>;
5
5
  fetch(repoName: string, cachebusterToken: string): Promise<Map<string, string>>;
6
6
  createAppVariantHierarchyItem(appVariantId: string, version: string): void;
7
- updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: ReadonlyMap<string, string>, appVariantId: string): Promise<void>;
7
+ updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>, appVariantId: string, prefix: string): Promise<void>;
8
8
  }
9
9
  export declare function determineProcessor(configuration: IConfiguration): IProcessor;
@@ -13,6 +13,7 @@ export default class CFUtil {
13
13
  private static getOrCreateServiceKeys;
14
14
  private static getServiceKeys;
15
15
  private static createServiceKey;
16
+ private static deleteServiceKeyUnsafe;
16
17
  private static getServiceInstance;
17
18
  static processErrors(json: any): void;
18
19
  static requestCfApi(url: string): Promise<IResource[]>;
@@ -20,6 +21,52 @@ export default class CFUtil {
20
21
  private static cfExecute;
21
22
  private static errorsToString;
22
23
  private static parseJson;
24
+ /**
25
+ * Get service keys and return the first one with valid endpoints
26
+ * @private
27
+ * @static
28
+ * @param {string} serviceInstanceGuid the service instance guid
29
+ * @return {Promise<any>} the first service key with valid endpoints, or null if none found
30
+ * @memberof CFUtil
31
+ */
32
+ private static getServiceKeyWithValidEndpoints;
33
+ /**
34
+ * Get service keys for a service instance by name. If the existing service key
35
+ * has endpoints as strings instead of objects, a new service key will be created.
36
+ * @static
37
+ * @param {string} serviceInstanceName name of the service instance
38
+ * @param {string} [spaceGuid] optional space guid, will use current space if not provided
39
+ * @return {Promise<any>} promise with service key credentials
40
+ * @memberof CFUtil
41
+ */
42
+ static getOrCreateServiceKeyWithEndpoints(serviceInstanceName: string, spaceGuid?: string): Promise<any>;
43
+ /**
44
+ * Check if endpoints object has at least one property that is an object
45
+ * @private
46
+ * @static
47
+ * @param {any} endpoints the endpoints object to validate
48
+ * @return {boolean} true if at least one property of endpoints is an object
49
+ * @memberof CFUtil
50
+ */
51
+ private static hasValidEndpoints;
52
+ /**
53
+ * Get all service key names for a service instance
54
+ * @private
55
+ * @static
56
+ * @param {string} serviceInstanceGuid the service instance guid
57
+ * @return {Promise<string[]>} promise with array of service key names
58
+ * @memberof CFUtil
59
+ */
60
+ private static getAllServiceKeyNames;
61
+ /**
62
+ * Generate a unique service key name in format serviceInstanceName-key-N
63
+ * @static
64
+ * @param {string} serviceInstanceName the service instance name
65
+ * @param {string} serviceInstanceGuid the service instance guid
66
+ * @return {Promise<string>} promise with unique service key name
67
+ * @memberof CFUtil
68
+ */
69
+ static generateUniqueServiceKeyName(serviceInstanceName: string, serviceInstanceGuid: string): Promise<string>;
23
70
  /**
24
71
  * Get space guid from configuration or local CF fodler
25
72
  * @static