@ui5/task-adaptation 1.6.5-rc.3 → 1.6.5

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.
Files changed (61) hide show
  1. package/.env.test +1 -0
  2. package/CHANGELOG.md +10 -57
  3. package/README.md +4 -0
  4. package/dist/adapters/abapAdapter.d.ts +7 -5
  5. package/dist/adapters/abapAdapter.js +9 -11
  6. package/dist/adapters/adapter.d.ts +9 -5
  7. package/dist/adapters/adapter.js +20 -0
  8. package/dist/adapters/cfAdapter.d.ts +8 -5
  9. package/dist/adapters/cfAdapter.js +16 -10
  10. package/dist/adapters/commands/command.d.ts +26 -23
  11. package/dist/adapters/commands/command.js +73 -46
  12. package/dist/adapters/commands/fetchEndpointsCommand.d.ts +9 -0
  13. package/dist/adapters/commands/fetchEndpointsCommand.js +21 -0
  14. package/dist/adapters/commands/fetchPreviewResourcesCommand.d.ts +15 -0
  15. package/dist/adapters/commands/fetchPreviewResourcesCommand.js +102 -0
  16. package/dist/adapters/commands/filterFilesCommand.d.ts +4 -0
  17. package/dist/adapters/commands/filterFilesCommand.js +22 -0
  18. package/dist/adapters/commands/omitDeletedResourcesCommand.d.ts +10 -0
  19. package/dist/adapters/commands/omitDeletedResourcesCommand.js +31 -0
  20. package/dist/adapters/commands/processPreviewResourcesCommand.d.ts +9 -0
  21. package/dist/adapters/commands/processPreviewResourcesCommand.js +51 -0
  22. package/dist/adapters/commands/renameFilesCommand.d.ts +20 -0
  23. package/dist/{util/filesUtil.js → adapters/commands/renameFilesCommand.js} +34 -43
  24. package/dist/adapters/commands/writeResourcesCommand.d.ts +7 -0
  25. package/dist/adapters/commands/writeResourcesCommand.js +20 -0
  26. package/dist/adapters/commands/xsAppJsonEnhanceRoutesCommand.d.ts +3 -3
  27. package/dist/adapters/commands/xsAppJsonEnhanceRoutesCommand.js +4 -4
  28. package/dist/adapters/previewAdapter.d.ts +12 -6
  29. package/dist/adapters/previewAdapter.js +28 -7
  30. package/dist/{appVariantManager.d.ts → appVariant.d.ts} +2 -9
  31. package/dist/{appVariantManager.js → appVariant.js} +14 -26
  32. package/dist/{baseAppManager.js → baseApp.js} +6 -12
  33. package/dist/bundle.js +53 -12
  34. package/dist/cache/cacheHolder.d.ts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +34 -69
  37. package/dist/model/configuration.d.ts +1 -0
  38. package/dist/model/configuration.js +1 -0
  39. package/dist/model/types.d.ts +9 -2
  40. package/dist/repositories/localRepository.d.ts +2 -3
  41. package/dist/repositories/localRepository.js +54 -33
  42. package/dist/util/cf/xsAppJsonUtil.d.ts +16 -3
  43. package/dist/util/cf/xsAppJsonUtil.js +7 -18
  44. package/dist/util/commonUtil.d.ts +6 -0
  45. package/dist/util/commonUtil.js +17 -0
  46. package/dist/util/fsUtil.d.ts +1 -2
  47. package/dist/util/fsUtil.js +12 -33
  48. package/dist/util/renamingUtil.js +1 -1
  49. package/dist/util/resourceUtil.js +1 -1
  50. package/dist/util/validator/abapValidator.js +2 -2
  51. package/dist/util/validator/validator.d.ts +1 -0
  52. package/dist/util/validator/validator.js +11 -0
  53. package/package.json +11 -12
  54. package/.hyperspace/pull_request_bot.json +0 -19
  55. package/dist/previewManager.d.ts +0 -14
  56. package/dist/previewManager.js +0 -147
  57. package/dist/util/filesUtil.d.ts +0 -19
  58. package/scripts/metadataDownloadHelper.ts +0 -88
  59. package/scripts/publish.ts +0 -256
  60. package/scripts/test-integration-prep.sh +0 -4
  61. /package/dist/{baseAppManager.d.ts → baseApp.d.ts} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { trimExtension } from "./util/commonUtil.js";
2
+ import { validateAppId } from "./util/validator/validator.js";
2
3
  const IGNORE_FILES = [
3
4
  "manifest-bundle.zip",
4
5
  "Component-preload.js",
@@ -72,22 +73,15 @@ export default class BaseApp {
72
73
  }
73
74
  return "i18n/i18n";
74
75
  }
75
- VALIDATION_RULES = new Map([["sap.app/id", (value) => {
76
- if (!value.includes(".")) {
77
- // https://help.sap.com/docs/bas/developing-sap-fiori-app-in-sap-business-application-studio/releasing-sap-fiori-application-to-be-extensible-in-adaptation-projects-on-sap-s-4hana-cloud
78
- // In the manifest.json file, make sure that the attribute
79
- // sap.app/id has at least 2 segments.
80
- throw new Error(`The original application id '${value}' should consist of multiple segments split by dot, e.g.: original.id`);
81
- }
82
- }]]);
76
+ VALIDATION_RULES = new Map([["sap.app/id", validateAppId]]);
83
77
  validateProperty(value, property) {
84
78
  if (!value) {
85
79
  throw new Error(`Original application manifest should have ${property}`);
86
80
  }
87
- let validatationRule = this.VALIDATION_RULES.get(property);
88
- if (validatationRule) {
89
- validatationRule(value);
81
+ let validationRule = this.VALIDATION_RULES.get(property);
82
+ if (validationRule) {
83
+ validationRule(value);
90
84
  }
91
85
  }
92
86
  }
93
- //# sourceMappingURL=baseAppManager.js.map
87
+ //# sourceMappingURL=baseApp.js.map
package/dist/bundle.js CHANGED
@@ -6301,7 +6301,7 @@ class _URL extends URL {
6301
6301
  }
6302
6302
  }
6303
6303
 
6304
- var rAmpersand = /&/g, rApplicationGroupID = /^\w+$/, sClassName$2 = "sap.ui.model.odata.v4.lib._Helper", rEquals = /\=/g, rGroupID = /^(\$auto(\.\w+)?|\$direct|\w+)$/, rHash = /#/g, rNotMetaContext = /\([^/]*|\/-?\d+/g, rPlus = /\+/g, rSingleQuote = /'/g, rSingleQuoteTwice = /''/g, rWhitespace = /\s+/g, _Helper;
6304
+ var rAmpersand = /&/g, rApplicationGroupID = /^\w+$/, sClassName$2 = "sap.ui.model.odata.v4.lib._Helper", rEndsWithTransientPredicate = /\(\$uid=[-\w]+\)$/, rEquals = /\=/g, rGroupID = /^(\$auto(\.\w+)?|\$direct|\w+)$/, rHash = /#/g, rNotMetaContext = /\([^/]*|\/-?\d+/g, rPlus = /\+/g, rSingleQuote = /'/g, rSingleQuoteTwice = /''/g, rWhitespace = /\s+/g, _Helper;
6305
6305
  _Helper = {
6306
6306
  addByPath: function (mMap, sPath, oItem) {
6307
6307
  if (oItem) {
@@ -6495,6 +6495,19 @@ _Helper = {
6495
6495
  convert(mQueryOptions, "");
6496
6496
  return aPaths;
6497
6497
  },
6498
+ copyETags: function (oSource, oTarget) {
6499
+ if (_Helper.getPrivateAnnotation(oSource, "predicate") !== _Helper.getPrivateAnnotation(oTarget, "predicate")) {
6500
+ return;
6501
+ }
6502
+ oTarget["@odata.etag"] = oSource["@odata.etag"];
6503
+ for (const sKey in oSource) {
6504
+ const vSourceProperty = oSource[sKey];
6505
+ const vTargetProperty = oTarget[sKey];
6506
+ if (vSourceProperty && vTargetProperty && typeof vSourceProperty === "object" && !Array.isArray(vSourceProperty)) {
6507
+ _Helper.copyETags(vSourceProperty, vTargetProperty);
6508
+ }
6509
+ }
6510
+ },
6498
6511
  copyPrivateAnnotation: function (oSource, sAnnotation, oTarget, bAllowSame) {
6499
6512
  if (_Helper.hasPrivateAnnotation(oSource, sAnnotation)) {
6500
6513
  if (_Helper.hasPrivateAnnotation(oTarget, sAnnotation)) {
@@ -6540,6 +6553,10 @@ _Helper = {
6540
6553
  if (typeof oResult.message === "object") {
6541
6554
  oResult.message = oResult.error.message.value;
6542
6555
  }
6556
+ const oClone = oResult.error["@$ui5.originalMessage"] = _Helper.clone(oResult.error);
6557
+ oResult.error.details?.forEach(function (oDetail, i) {
6558
+ oDetail["@$ui5.originalMessage"] = oClone.details[i];
6559
+ });
6543
6560
  } catch (e) {
6544
6561
  Log.warning(e.toString(), sBody, sClassName$2);
6545
6562
  }
@@ -6583,7 +6600,7 @@ _Helper = {
6583
6600
  };
6584
6601
  },
6585
6602
  createTechnicalDetails: function (oMessage) {
6586
- var oClonedMessage, oError = oMessage["@$ui5.error"], oOriginalMessage = oMessage["@$ui5.originalMessage"] || oMessage, oTechnicalDetails = {};
6603
+ var oClonedMessage, oError = oMessage["@$ui5.error"], oOriginalMessage = oMessage["@$ui5.originalMessage"], oTechnicalDetails = {};
6587
6604
  if (oError && (oError.status || oError.cause)) {
6588
6605
  oError = oError.cause || oError;
6589
6606
  oTechnicalDetails.httpStatus = oError.status;
@@ -6594,7 +6611,7 @@ _Helper = {
6594
6611
  oTechnicalDetails.retryAfter = oError.retryAfter;
6595
6612
  }
6596
6613
  }
6597
- if (!(oOriginalMessage instanceof Error)) {
6614
+ if (oOriginalMessage) {
6598
6615
  Object.defineProperty(oTechnicalDetails, "originalMessage", {
6599
6616
  enumerable: true,
6600
6617
  get: function () {
@@ -6709,9 +6726,11 @@ _Helper = {
6709
6726
  message: oMessage.message,
6710
6727
  numericSeverity: iNumericSeverity,
6711
6728
  technical: bTechnical || oMessage.technical,
6712
- "@$ui5.error": oError,
6713
- "@$ui5.originalMessage": oMessage
6729
+ "@$ui5.error": oError
6714
6730
  };
6731
+ if (!(oMessage instanceof Error)) {
6732
+ oRawMessage["@$ui5.originalMessage"] = oMessage["@$ui5.originalMessage"] ?? oMessage;
6733
+ }
6715
6734
  Object.keys(oMessage).forEach(function (sProperty) {
6716
6735
  if (sProperty[0] === "@") {
6717
6736
  if (sProperty.endsWith(".numericSeverity")) {
@@ -7016,6 +7035,28 @@ _Helper = {
7016
7035
  }
7017
7036
  return mURLParameters;
7018
7037
  },
7038
+ getUsedPaths: function (aPaths, mQueryOptions) {
7039
+ function isRelated(sPath0, sPath1) {
7040
+ return _Helper.hasPathPrefix(sPath0, sPath1) || _Helper.hasPathPrefix(sPath1, sPath0);
7041
+ }
7042
+ function isUsed(sPath, mQueryOptionsForPath) {
7043
+ let bPathMatchedExpand = false;
7044
+ const aSelect = mQueryOptionsForPath.$select || ["*"];
7045
+ const mExpand = mQueryOptionsForPath.$expand || ({});
7046
+ return sPath === "*" || (sPath in mExpand) || aSelect.includes(sPath) || aSelect.some(sSelect => isRelated(sPath, sSelect)) || Object.keys(mExpand).some(sExpand => {
7047
+ if (_Helper.hasPathPrefix(sPath, sExpand)) {
7048
+ bPathMatchedExpand = true;
7049
+ return isUsed(sPath.slice(sExpand.length + 1), mExpand[sExpand]);
7050
+ }
7051
+ if (_Helper.hasPathPrefix(sExpand, sPath)) {
7052
+ bPathMatchedExpand = true;
7053
+ return true;
7054
+ }
7055
+ return false;
7056
+ }) || !bPathMatchedExpand && aSelect.includes("*");
7057
+ }
7058
+ return aPaths.filter(sPath => isUsed(sPath, mQueryOptions));
7059
+ },
7019
7060
  hasPathPrefix: function (sPath, sBasePath) {
7020
7061
  return _Helper.getRelativePath(sPath, sBasePath) !== undefined;
7021
7062
  },
@@ -7134,6 +7175,9 @@ _Helper = {
7134
7175
  }
7135
7176
  return mResult;
7136
7177
  },
7178
+ isAffectedBy: function (sPropertyPath, sSideEffectsPath) {
7179
+ return _Helper.hasPathPrefix(sPropertyPath, sSideEffectsPath.endsWith("/*") ? sSideEffectsPath.slice(0, -2) : sSideEffectsPath);
7180
+ },
7137
7181
  isDataAggregation: function (mParameters) {
7138
7182
  return mParameters && mParameters.$$aggregation && !mParameters.$$aggregation.hierarchyQualifier;
7139
7183
  },
@@ -7190,13 +7234,7 @@ _Helper = {
7190
7234
  return oURL.origin === new URL(document.baseURI).origin ? oURL.toString().slice(oURL.origin.length) : oURL.toString();
7191
7235
  },
7192
7236
  makeAbsoluteLongtextUrl: function (oMessage, sBase) {
7193
- if (oMessage.longtextUrl) {
7194
- const sAbsoluteLongtextURL = _Helper.makeAbsolute(oMessage.longtextUrl, sBase);
7195
- if (oMessage.longtextUrl !== sAbsoluteLongtextURL) {
7196
- oMessage["@$ui5.originalMessage"] ??= _Helper.clone(oMessage);
7197
- oMessage.longtextUrl = sAbsoluteLongtextURL;
7198
- }
7199
- }
7237
+ oMessage.longtextUrl &&= _Helper.makeAbsolute(oMessage.longtextUrl, sBase);
7200
7238
  },
7201
7239
  makeRelativePath: function (sPath, sBase) {
7202
7240
  return new _URL(sPath).relativeTo(new _URL(sBase));
@@ -7212,6 +7250,9 @@ _Helper = {
7212
7250
  return oResult;
7213
7251
  }, vValue);
7214
7252
  },
7253
+ matchEndsWithTransientPredicate: function (sPath) {
7254
+ return rEndsWithTransientPredicate.exec(sPath);
7255
+ },
7215
7256
  merge: fnMerge,
7216
7257
  mergeQueryOptions: function (mQueryOptions, sOrderby, aFilters) {
7217
7258
  var mResult;
@@ -1,6 +1,6 @@
1
1
  export default class CacheHolder {
2
2
  private static TEMP_TASK_DIR;
3
- private static getTempDir;
3
+ static getTempDir(...paths: string[]): string;
4
4
  static read(repoName: string, token: string): Promise<Map<string, string>>;
5
5
  static write(repoName: string, token: string, files: Map<string, string>): Promise<void>;
6
6
  private static isValid;
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ import { ITaskParameters } from "./model/types.js";
2
2
  /**
3
3
  * Creates an appVariant bundle from the provided resources.
4
4
  */
5
- declare const _default: ({ workspace, options, taskUtil }: ITaskParameters) => Promise<void>;
5
+ declare const _default: ({ workspace, options, taskUtil }: ITaskParameters) => Promise<ReadonlyMap<string, string>>;
6
6
  export default _default;
package/dist/index.js CHANGED
@@ -1,79 +1,44 @@
1
1
  import * as dotenv from "dotenv";
2
- import { logBuilderVersion } from "./util/commonUtil.js";
3
- import AppVariant from "./appVariantManager.js";
4
- import BaseApp from "./baseAppManager.js";
5
- import ResourceUtil from "./util/resourceUtil.js";
6
- import FilesUtil from "./util/filesUtil.js";
7
- import PreviewManager from "./previewManager.js";
2
+ import { getReferences, logBuilderVersion } from "./util/commonUtil.js";
3
+ import AppVariant from "./appVariant.js";
4
+ import BaseApp from "./baseApp.js";
8
5
  import { initialize } from "./landscapeConfiguration.js";
9
6
  /**
10
7
  * Creates an appVariant bundle from the provided resources.
11
8
  */
12
- export default ({ workspace, options, taskUtil }) => {
9
+ export default async ({ workspace, options, taskUtil }) => {
10
+ const ui5BuilderTools = {
11
+ workspace,
12
+ projectNamespace: options.projectNamespace,
13
+ taskUtil,
14
+ };
13
15
  dotenv.config();
14
- async function process(workspace, taskUtil) {
15
- logBuilderVersion();
16
- const { repository, adapter } = initialize(options.configuration);
17
- const adaptationProject = await AppVariant.fromWorkspace(workspace, options.projectNamespace);
18
- const previewManagerPromise = PreviewManager.createFromRoot(adaptationProject.reference, repository, options.configuration);
19
- const appVariantIdHierarchy = await repository.getAppVariantIdHierarchy(adaptationProject.reference);
20
- if (appVariantIdHierarchy.length === 0) {
21
- throw new Error(`No app variant found for reference ${adaptationProject.reference}`);
22
- }
23
- // appVariantIdHierarchy contains original application on bottom and the
24
- // latest app variant on top. We reverse the list to process original
25
- // application first and then app variants in chronological order.
26
- const reversedHierarchy = appVariantIdHierarchy.toReversed();
27
- const fetchFilesPromises = reversedHierarchy.map(variant => repository.fetch(variant));
28
- fetchFilesPromises.push(Promise.resolve(adaptationProject.files));
29
- const appVariants = new Array();
30
- const adapt = async (baseAppFiles, 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);
39
- const adaptCommandChain = adapter.createAdaptCommandChain(baseApp, appVariant);
40
- const mergeCommandChain = adapter.createMergeCommandChain(baseApp, appVariant);
41
- const adaptedFiles = await adaptCommandChain.execute();
42
- const mergedFiles = await mergeCommandChain.execute(adaptedFiles);
43
- return mergedFiles;
44
- };
45
- let files = await fetchFilesPromises.reduce(async (previousFiles, currentFiles) => adapt(await previousFiles, await currentFiles), fetchFilesPromises.shift());
46
- files = await adapter.createPostCommandChain().execute(files);
47
- const references = getReferences(appVariants, adaptationProject.id);
48
- files = FilesUtil.filter(files);
49
- files = FilesUtil.rename(files, references);
50
- adaptationProject.omitDeletedResources(files, options.projectNamespace, taskUtil);
51
- // Read libs for preview
52
- const previewManager = await previewManagerPromise;
53
- const writePromises = new Array(previewManager.processPreviewResources(files));
54
- files.forEach((content, filename) => {
55
- const resource = ResourceUtil.createResource(filename, options.projectNamespace, content);
56
- writePromises.push(workspace.write(resource));
57
- });
58
- await Promise.all(writePromises);
16
+ logBuilderVersion();
17
+ const { repository, adapter } = initialize(options.configuration);
18
+ const adaptationProject = await AppVariant.fromWorkspace(workspace, options.projectNamespace);
19
+ const setupCommandChain = adapter.createSetupCommandChain(adaptationProject.reference, repository);
20
+ await setupCommandChain.execute();
21
+ const appVariantIdHierarchy = await repository.getAppVariantIdHierarchy(adaptationProject.reference);
22
+ if (appVariantIdHierarchy.length === 0) {
23
+ throw new Error(`No app variant found for reference ${adaptationProject.reference}`);
59
24
  }
60
- return process(workspace, taskUtil);
61
- /**
62
- * 4p. Reference map contains searchTerm as key and replacement as value. Base
63
- * id and app variant ids are renamed to adaptation project id.
64
- */
65
- function getReferences(appVariants, adaptationProjectId) {
66
- const references = new Map();
67
- appVariants.forEach((variant, i) => {
68
- if (i === 0) {
69
- references.set(variant.reference, adaptationProjectId);
70
- }
71
- if (variant.id !== adaptationProjectId) {
72
- references.set(variant.id, adaptationProjectId);
73
- }
74
- variant.getRenamingForMovedFiles().forEach((value, key) => references.set(key, value));
75
- });
76
- return references;
25
+ // appVariantIdHierarchy contains original application on bottom and the
26
+ // latest app variant on top. We reverse the list to process original
27
+ // application first and then app variants in chronological order.
28
+ const reversedHierarchy = appVariantIdHierarchy.toReversed();
29
+ const fetchFilesPromises = reversedHierarchy.map(variant => repository.fetch(variant));
30
+ const appVariants = new Array();
31
+ const adapt = async (baseFiles, appVariant) => {
32
+ appVariants.push(appVariant);
33
+ const baseApp = BaseApp.fromFiles(baseFiles);
34
+ return adapter.createAdaptCommandChain(baseApp, appVariant).execute();
35
+ };
36
+ let files = await fetchFilesPromises.shift(); // original app files
37
+ for (const appVariantFiles of fetchFilesPromises) {
38
+ files = await adapt(files, AppVariant.fromFiles(await appVariantFiles));
77
39
  }
40
+ files = await adapt(files, adaptationProject);
41
+ const references = getReferences(appVariants, adaptationProject.id);
42
+ return adapter.createPostCommandChain(references, adaptationProject, ui5BuilderTools).execute(files);
78
43
  };
79
44
  //# sourceMappingURL=index.js.map
@@ -22,3 +22,4 @@ export interface IAbapTargetMeta {
22
22
  name?: string;
23
23
  ignoreCertErrors?: boolean;
24
24
  }
25
+ export declare const REUSE_DIR = ".adp/reuse";
@@ -1,2 +1,3 @@
1
1
  export const LANDSCAPE_TYPES = ["cf", "abap"];
2
+ export const REUSE_DIR = ".adp/reuse";
2
3
  //# sourceMappingURL=configuration.js.map
@@ -1,3 +1,4 @@
1
+ import TaskUtil from "@ui5/project/build/helpers/TaskUtil";
1
2
  import { IConfiguration } from "./configuration.js";
2
3
  import Language from "./language.js";
3
4
  export interface IProjectOptions {
@@ -74,9 +75,14 @@ export interface IChangeContent {
74
75
  supportedLocales?: string[];
75
76
  }
76
77
  export interface ITaskParameters {
77
- workspace: any;
78
+ workspace: IWorkspace;
78
79
  options: IProjectOptions;
79
- taskUtil: any;
80
+ taskUtil: TaskUtil;
81
+ }
82
+ export interface UI5BuilderTools {
83
+ workspace: IWorkspace;
84
+ projectNamespace: string;
85
+ taskUtil: TaskUtil;
80
86
  }
81
87
  export interface IBaseAppInfo {
82
88
  filepath: string;
@@ -123,4 +129,5 @@ export type ServiceCredentials = {
123
129
  } | string>;
124
130
  "sap.cloud.service"?: string;
125
131
  };
132
+ export type FetchFilesPromise = Map<string, Promise<ReadonlyMap<string, string>>>;
126
133
  export { IConfiguration };
@@ -7,7 +7,6 @@ export default class LocalRepository implements IRepository {
7
7
  downloadAnnotationFile(_uri: string): Promise<Map<string, string>>;
8
8
  getAppVariantIdHierarchy(appId: string): Promise<ILocalResource[]>;
9
9
  fetch(resource: ILocalResource): Promise<Map<string, string>>;
10
- private collectAppVariantIdHierarchyItems;
11
- private getExistingManifestPath;
12
- private getLocalFilesDirectory;
10
+ private buildManifestIndex;
11
+ private getLocalFilesDir;
13
12
  }
@@ -1,6 +1,7 @@
1
1
  import { getLogger } from "@ui5/logger";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
+ import { glob } from "glob";
4
5
  import FsUtil from "../util/fsUtil.js";
5
6
  const log = getLogger("@ui5/task-adaptation::LocalRepository");
6
7
  export default class LocalRepository {
@@ -10,48 +11,68 @@ export default class LocalRepository {
10
11
  }
11
12
  async getAppVariantIdHierarchy(appId) {
12
13
  const items = [];
13
- await this.collectAppVariantIdHierarchyItems(appId, items);
14
+ const baseDir = this.getLocalFilesDir();
15
+ const index = await this.buildManifestIndex(baseDir);
16
+ const visited = new Set();
17
+ let currentAppId = appId;
18
+ while (currentAppId) {
19
+ if (visited.has(currentAppId)) {
20
+ throw new Error(`Cycle detected in app variant hierarchy: '${currentAppId}' was already visited. Visited IDs: ${[...visited].join(" -> ")} -> ${currentAppId}`);
21
+ }
22
+ visited.add(currentAppId);
23
+ const entry = index.get(currentAppId);
24
+ if (!entry) {
25
+ throw new Error(`App '${currentAppId}' not found in local directory '${baseDir}'.`);
26
+ }
27
+ items.push({
28
+ appName: currentAppId,
29
+ cacheBusterToken: Promise.resolve("local"),
30
+ absolutePath: entry.absolutePath,
31
+ });
32
+ if (entry.isVariant) {
33
+ if (!entry.reference || typeof entry.reference !== "string") {
34
+ throw new Error(`Invalid or missing 'reference' for app variant '${currentAppId}': expected a non-empty string but got '${entry.reference}'`);
35
+ }
36
+ currentAppId = entry.reference;
37
+ }
38
+ else {
39
+ currentAppId = undefined;
40
+ }
41
+ }
14
42
  return items;
15
43
  }
16
44
  fetch(resource) {
17
45
  log.verbose(`Fetching base app files from local directory: ${resource.absolutePath}`);
18
46
  return FsUtil.readFilesRecursively(resource.absolutePath);
19
47
  }
20
- async collectAppVariantIdHierarchyItems(appId, items) {
21
- const appDir = this.getLocalFilesDirectory(appId);
22
- const filenames = [
23
- { path: ["webapp", "manifest.json"], suffix: "" },
24
- { path: ["webapp", "manifest.appdescr_variant"], suffix: "" },
25
- { path: ["manifest.json"], suffix: "-opt-static-abap" },
26
- { path: ["manifest.appdescr_variant"], suffix: "-opt-static-abap" },
27
- ];
28
- const filepaths = filenames.map((filename) => path.join(appDir + filename.suffix, ...filename.path));
29
- const existingManifestPath = await this.getExistingManifestPath(filepaths);
30
- items.push({
31
- appName: appId,
32
- cacheBusterToken: Promise.resolve("local"),
33
- absolutePath: path.dirname(existingManifestPath),
34
- });
35
- if (existingManifestPath.endsWith("manifest.appdescr_variant")) {
36
- const manifestContent = await fs.readFile(existingManifestPath, "utf-8");
37
- const manifest = JSON.parse(manifestContent);
38
- await this.collectAppVariantIdHierarchyItems(manifest.reference, items);
39
- }
40
- }
41
- async getExistingManifestPath(filePaths) {
42
- const existenceChecks = await Promise.all(filePaths.map((filePath) => FsUtil.exists(filePath)));
43
- const existingIndex = existenceChecks.findIndex((exists) => exists);
44
- if (existingIndex !== -1) {
45
- return filePaths[existingIndex];
48
+ async buildManifestIndex(baseDir) {
49
+ const index = new Map();
50
+ const manifestPaths = await glob("**/manifest.{json,appdescr_variant}", { cwd: baseDir, absolute: true, dot: true });
51
+ for (const manifestPath of manifestPaths) {
52
+ const content = await fs.readFile(manifestPath, "utf-8");
53
+ const manifest = JSON.parse(content);
54
+ const isVariant = manifestPath.endsWith("manifest.appdescr_variant");
55
+ const id = isVariant ? manifest.id : manifest["sap.app"]?.id;
56
+ if (typeof id === "string" && id) {
57
+ index.set(id, {
58
+ absolutePath: path.dirname(manifestPath),
59
+ reference: manifest.reference,
60
+ isVariant,
61
+ });
62
+ }
46
63
  }
47
- throw new Error(`None of the paths exist: '${filePaths.join("', '")}'.`);
64
+ return index;
48
65
  }
49
- getLocalFilesDirectory(appId) {
66
+ getLocalFilesDir() {
50
67
  const adpDirConfigured = process.env.ADP_BUILDER_DIR;
51
- const adpDir = adpDirConfigured
52
- ? path.normalize(adpDirConfigured).replace(/\\/g, "/").replace(/\/+$/, "").split("/")
53
- : [".."];
54
- return path.join(process.cwd(), ...adpDir, appId);
68
+ if (adpDirConfigured) {
69
+ const unix = adpDirConfigured.replace(/\\/g, "/").replace(/\/+$/, "");
70
+ if (/^[A-Za-z]:/.test(adpDirConfigured) || path.isAbsolute(unix)) {
71
+ return unix;
72
+ }
73
+ return path.join(process.cwd(), unix);
74
+ }
75
+ throw new Error("Environment variable 'ADP_BUILDER_DIR' is not set. Please set it to the directory containing the local app files.");
55
76
  }
56
77
  }
57
78
  //# sourceMappingURL=localRepository.js.map
@@ -1,4 +1,17 @@
1
- import { IConfiguration, ServiceCredentials } from "../../model/types.js";
1
+ import { ServiceCredentials } from "../../model/types.js";
2
+ export type XsApp = {
3
+ welcomeFile?: string;
4
+ authenticationMethod: "none" | "route";
5
+ routes: Route[];
6
+ };
7
+ export type Route = {
8
+ source: string;
9
+ target: string;
10
+ service?: string;
11
+ destination?: string;
12
+ authenticationType?: string;
13
+ localDir?: string;
14
+ };
2
15
  export declare const XSAPP_JSON_FILENAME = "xs-app.json";
3
16
  /**
4
17
  * Merges multiple xs-app.json contents into one. AppVariant and Reuse Library
@@ -10,5 +23,5 @@ export declare const XSAPP_JSON_FILENAME = "xs-app.json";
10
23
  */
11
24
  export declare function merge(xsAppFiles: string[]): string | undefined;
12
25
  export declare function enhanceRoutesWithEndpointAndService(xsAppJsonContent: string, serviceCredentials: ServiceCredentials): string;
13
- export declare function enhanceRoutes(serviceCredentials: ServiceCredentials, baseRoutes: any): any;
14
- export declare function fetchCredentialsAndEnhanceRoutes(xsAppJson: string, { serviceInstanceName, space, appName }: IConfiguration): Promise<string>;
26
+ export declare function enhanceRoutes(serviceCredentials: ServiceCredentials, baseRoutes: Route[]): Route[];
27
+ export declare function fetchCredentialsAndEnhanceRoutes(xsAppJson: string, serviceCredentialsPromise: Promise<ServiceCredentials | undefined>): Promise<string>;
@@ -1,5 +1,4 @@
1
1
  import { getLogger } from "@ui5/logger";
2
- import CFUtil from "../cfUtil.js";
3
2
  const log = getLogger("@ui5/task-adaptation::XSAppJsonUtil");
4
3
  export const XSAPP_JSON_FILENAME = "xs-app.json";
5
4
  /**
@@ -63,7 +62,7 @@ export function enhanceRoutes(serviceCredentials, baseRoutes) {
63
62
  return acc;
64
63
  }, {});
65
64
  return baseRoutes.map((route) => {
66
- const endpointName = destinationToEndpoint[route.destination];
65
+ const endpointName = route.destination && destinationToEndpoint[route.destination];
67
66
  if (endpointName) {
68
67
  // There is a matching endpoint: remove destination and add endpoint/service
69
68
  const { destination: _destination, ...rest } = route;
@@ -79,22 +78,12 @@ export function enhanceRoutes(serviceCredentials, baseRoutes) {
79
78
  }
80
79
  });
81
80
  }
82
- export async function fetchCredentialsAndEnhanceRoutes(xsAppJson, { serviceInstanceName, space, appName }) {
83
- if (!serviceInstanceName) {
84
- throw new Error(`Service instance name must be specified in ui5.yaml configuration for app '${appName}'`);
81
+ export async function fetchCredentialsAndEnhanceRoutes(xsAppJson, serviceCredentialsPromise) {
82
+ const serviceCredentials = await serviceCredentialsPromise;
83
+ if (!serviceCredentials) {
84
+ log.verbose(`No service credentials available. Skipping xs-app.json update.`);
85
+ return xsAppJson;
85
86
  }
86
- let serviceCredentials;
87
- try {
88
- // Get valid service keys with proper endpoints structure
89
- serviceCredentials = await CFUtil.getOrCreateServiceKeyWithEndpoints(serviceInstanceName, space);
90
- }
91
- catch (error) {
92
- throw new Error(`Failed to get valid service keys for app '${appName}': ${error.message}`);
93
- }
94
- if (serviceCredentials) {
95
- return enhanceRoutesWithEndpointAndService(xsAppJson, serviceCredentials);
96
- }
97
- log.info(`No endpoints found for app '${appName}'. xs-app.json will not be updated.`);
98
- return xsAppJson;
87
+ return enhanceRoutesWithEndpointAndService(xsAppJson, serviceCredentials);
99
88
  }
100
89
  //# sourceMappingURL=xsAppJsonUtil.js.map
@@ -1,5 +1,6 @@
1
1
  import { IConfiguration } from "../model/types.js";
2
2
  import Language from "../model/language.js";
3
+ import AppVariant from "../appVariant.js";
3
4
  export declare function dotToUnderscore(value: string): string;
4
5
  export declare function validateConfiguration<T extends object>(options: T, properties: Array<keyof T>): void;
5
6
  export declare function validateObject<T extends object>(options: T, properties: Array<keyof T>, message: string): void;
@@ -12,3 +13,8 @@ export declare function logBuilderVersion(): void;
12
13
  export declare function logBetaUsage(): void;
13
14
  export declare function getUniqueName(existingNames: string[], template: string): string;
14
15
  export declare function isManifestChange(filename: string, content: string): boolean;
16
+ /**
17
+ * 4p. Reference map contains searchTerm as key and replacement as value. Base
18
+ * id and app variant ids are renamed to adaptation project id.
19
+ */
20
+ export declare function getReferences(appVariants: AppVariant[], adaptationProjectId: string): Map<string, string>;
@@ -98,4 +98,21 @@ export function isManifestChange(filename, content) {
98
98
  }
99
99
  return false;
100
100
  }
101
+ /**
102
+ * 4p. Reference map contains searchTerm as key and replacement as value. Base
103
+ * id and app variant ids are renamed to adaptation project id.
104
+ */
105
+ export function getReferences(appVariants, adaptationProjectId) {
106
+ const references = new Map();
107
+ appVariants.forEach((variant, i) => {
108
+ if (i === 0) {
109
+ references.set(variant.reference, adaptationProjectId);
110
+ }
111
+ if (variant.id !== adaptationProjectId) {
112
+ references.set(variant.id, adaptationProjectId);
113
+ }
114
+ variant.getRenamingForMovedFiles().forEach((value, key) => references.set(key, value));
115
+ });
116
+ return references;
117
+ }
101
118
  //# sourceMappingURL=commonUtil.js.map
@@ -1,5 +1,5 @@
1
1
  export default class FsUtil {
2
- static readInProject(filepath: string): Promise<string>;
2
+ static readInProject(filepath: string, encoding?: BufferEncoding): Promise<Buffer | string>;
3
3
  /**
4
4
  * Check whether a file or directory exists (non-throwing).
5
5
  * @param filePath Absolute or relative path
@@ -7,5 +7,4 @@ export default class FsUtil {
7
7
  */
8
8
  private static fileExists;
9
9
  static readFilesRecursively(rootDirectory: string): Promise<Map<string, string>>;
10
- static exists(filePath: string): Promise<boolean>;
11
10
  }
@@ -7,9 +7,9 @@ export default class FsUtil {
7
7
  * @param filepath The relative file path from the project root (e.g. 'ui5AppInfo.json').
8
8
  * @returns A promise that resolves to the file content as a string.
9
9
  */
10
- static async readInProject(filepath) {
10
+ static async readInProject(filepath, encoding) {
11
11
  try {
12
- return await fs.readFile(path.join(process.cwd(), filepath), "utf-8");
12
+ return await fs.readFile(path.join(process.cwd(), filepath), encoding);
13
13
  }
14
14
  catch (error) {
15
15
  const isProjectRoot = await FsUtil.fileExists(path.join(process.cwd(), "ui5.yaml"));
@@ -37,38 +37,17 @@ export default class FsUtil {
37
37
  }
38
38
  }
39
39
  static async readFilesRecursively(rootDirectory) {
40
- const directoriesToScan = [rootDirectory];
41
- const fileReadTasks = [];
42
- while (directoriesToScan.length > 0) {
43
- const currentDirectory = directoriesToScan.pop();
44
- if (!currentDirectory) {
45
- continue;
46
- }
47
- const entries = await fs.readdir(currentDirectory, { withFileTypes: true });
48
- for (const entry of entries) {
49
- const entryPath = path.join(currentDirectory, entry.name);
50
- if (entry.isDirectory()) {
51
- directoriesToScan.push(entryPath);
52
- }
53
- else if (entry.isFile()) {
54
- fileReadTasks.push((async () => {
55
- const relativeFilePath = path.relative(rootDirectory, entryPath);
56
- const content = await fs.readFile(entryPath, "utf-8");
57
- return [relativeFilePath, content];
58
- })());
59
- }
60
- }
61
- }
40
+ const entries = await fs.readdir(rootDirectory, { withFileTypes: true, recursive: true });
41
+ const fileReadTasks = entries
42
+ .filter(entry => entry.isFile())
43
+ .map(async (entry) => {
44
+ const parentPath = entry.parentPath ?? entry.path; // node v20.11.0 fallback
45
+ const entryPath = path.join(parentPath, entry.name);
46
+ const relativeFilePath = path.relative(rootDirectory, entryPath).replaceAll("\\", "/");
47
+ const content = await fs.readFile(entryPath, "utf-8");
48
+ return [relativeFilePath, content];
49
+ });
62
50
  return new Map(await Promise.all(fileReadTasks));
63
51
  }
64
- static async exists(filePath) {
65
- try {
66
- await fs.access(filePath);
67
- return true;
68
- }
69
- catch {
70
- return false;
71
- }
72
- }
73
52
  }
74
53
  //# sourceMappingURL=fsUtil.js.map