@ui5/task-adaptation 1.5.2 → 1.6.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.
Files changed (45) hide show
  1. package/.hyperspace/pull_request_bot.json +19 -0
  2. package/CHANGELOG.md +11 -6
  3. package/dist/appVariantManager.js +1 -1
  4. package/dist/baseAppManager.js +3 -3
  5. package/dist/buildStrategy.d.ts +0 -3
  6. package/dist/buildStrategy.js +0 -7
  7. package/dist/bundle.d.ts +4 -3
  8. package/dist/bundle.js +3650 -3595
  9. package/dist/cache/cacheHolder.d.ts +1 -1
  10. package/dist/cache/cacheHolder.js +8 -6
  11. package/dist/index.js +5 -1
  12. package/dist/model/authenticationError.d.ts +3 -0
  13. package/dist/model/authenticationError.js +8 -0
  14. package/dist/model/types.d.ts +12 -0
  15. package/dist/previewManager.d.ts +13 -0
  16. package/dist/previewManager.js +142 -0
  17. package/dist/processors/abapProcessor.d.ts +1 -0
  18. package/dist/processors/abapProcessor.js +3 -0
  19. package/dist/processors/cfProcessor.d.ts +2 -1
  20. package/dist/processors/cfProcessor.js +11 -1
  21. package/dist/processors/processor.d.ts +2 -1
  22. package/dist/repositories/html5RepoManager.d.ts +3 -2
  23. package/dist/repositories/html5RepoManager.js +18 -5
  24. package/dist/util/cfUtil.d.ts +8 -1
  25. package/dist/util/cfUtil.js +16 -8
  26. package/dist/util/movingHandler/changeFileMoveHandler.d.ts +8 -0
  27. package/dist/util/movingHandler/changeFileMoveHandler.js +77 -0
  28. package/dist/util/resourceUtil.d.ts +11 -3
  29. package/dist/util/resourceUtil.js +83 -18
  30. package/eslint.config.js +4 -3
  31. package/package.json +19 -20
  32. package/rollup/amdToEsm.ts +22 -0
  33. package/rollup/bundle.d.ts +25 -0
  34. package/rollup/bundleDefinition.js +19 -0
  35. package/rollup/bundler.ts +35 -0
  36. package/rollup/overrides/sap/base/config.js +59 -0
  37. package/rollup/overrides/sap/ui/fl/apply/_internal/flexObjects/AppDescriptorChange.js +68 -0
  38. package/rollup/overrides/sap/ui/performance/Measurement.js +4 -0
  39. package/rollup/project/package.json +4 -0
  40. package/rollup/project/ui5.yaml +13 -0
  41. package/rollup/project/webapp/manifest.json +5 -0
  42. package/rollup/rollup.ts +133 -0
  43. package/rollup/ui5Resolve.ts +145 -0
  44. package/scripts/publish.ts +256 -0
  45. package/scripts/test-integration-prep.sh +4 -0
@@ -1,7 +1,7 @@
1
1
  export default class CacheHolder {
2
2
  private static TEMP_TASK_DIR;
3
3
  private static getTempDir;
4
- static read(repoName: string, token: string): Map<string, string> | undefined;
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;
7
7
  /**
@@ -1,21 +1,22 @@
1
1
  import * as fs from "fs";
2
2
  import * as fsPromises from "fs/promises";
3
3
  import * as path from "path";
4
+ import * as os from "node:os";
4
5
  import ResourceUtil from "../util/resourceUtil.js";
5
6
  import encodeFilename from "filenamify";
6
7
  import { getLogger } from "@ui5/logger";
7
- import tempDir from "temp-dir";
8
8
  const log = getLogger("@ui5/task-adaptation::CacheHolder");
9
9
  export default class CacheHolder {
10
10
  static TEMP_TASK_DIR = "ui5-task-adaptation";
11
11
  static getTempDir(...paths) {
12
- return path.join(tempDir, this.TEMP_TASK_DIR, ...paths.map(part => encodeFilename(part, { replacement: "_" })));
12
+ return path.join(os.tmpdir(), this.TEMP_TASK_DIR, ...paths.map(part => encodeFilename(part, { replacement: "_" })));
13
13
  }
14
14
  static read(repoName, token) {
15
15
  const directory = this.getTempDir(repoName, token);
16
16
  if (this.isValid(repoName, "repoName") && this.isValid(token, "token") && fs.existsSync(directory)) {
17
- return ResourceUtil.read(directory);
17
+ return ResourceUtil.byGlob(directory, "**/*");
18
18
  }
19
+ return Promise.resolve(new Map());
19
20
  }
20
21
  static async write(repoName, token, files) {
21
22
  this.delete(repoName);
@@ -40,7 +41,7 @@ export default class CacheHolder {
40
41
  * Clears all cached files
41
42
  */
42
43
  static clear() {
43
- this.deleteDir(path.join(tempDir, this.TEMP_TASK_DIR));
44
+ this.deleteDir(path.join(os.tmpdir(), this.TEMP_TASK_DIR));
44
45
  }
45
46
  static deleteDir(directory) {
46
47
  if (fs.existsSync(directory)) {
@@ -67,9 +68,10 @@ export function cached() {
67
68
  return function (_target, _propertyKey, descriptor) {
68
69
  const originalValue = descriptor.value;
69
70
  descriptor.value = async function (...args) {
70
- let files = CacheHolder.read(args[0], args[1]);
71
+ let files = await CacheHolder.read(args[0], args[1]);
71
72
  CacheHolder.clearOutdatedExcept(args[0]);
72
- if (files == null) {
73
+ if (files.size === 0) {
74
+ log.verbose(`Cache repo '${args[0]}' with token '${args[1]}' does not contain files. Fetching...`);
73
75
  files = await originalValue.apply(this, args);
74
76
  await CacheHolder.write(args[0], args[1], files);
75
77
  }
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import I18nMerger from "./util/i18nMerger.js";
6
6
  import ResourceUtil from "./util/resourceUtil.js";
7
7
  import { determineProcessor } from "./processors/processor.js";
8
8
  import FilesUtil from "./util/filesUtil.js";
9
+ import PreviewManager from "./previewManager.js";
9
10
  /**
10
11
  * Creates an appVariant bundle from the provided resources.
11
12
  */
@@ -15,6 +16,7 @@ export default ({ workspace, options, taskUtil }) => {
15
16
  logBuilderVersion();
16
17
  const processor = determineProcessor(options.configuration);
17
18
  const adaptationProject = await AppVariant.fromWorkspace(workspace, options.projectNamespace);
19
+ const previewManagerPromise = PreviewManager.createFromRoot(adaptationProject.reference, processor);
18
20
  const appVariantIdHierarchy = await processor.getAppVariantIdHierarchy(adaptationProject.reference);
19
21
  if (appVariantIdHierarchy.length === 0) {
20
22
  throw new Error(`No app variant found for reference ${adaptationProject.reference}`);
@@ -43,7 +45,9 @@ export default ({ workspace, options, taskUtil }) => {
43
45
  files = FilesUtil.filter(files);
44
46
  files = FilesUtil.rename(files, references);
45
47
  adaptationProject.omitDeletedResources(files, options.projectNamespace, taskUtil);
46
- const writePromises = new Array();
48
+ // Read libs for preview
49
+ const previewManager = await previewManagerPromise;
50
+ const writePromises = new Array(previewManager.processPreviewResources(files));
47
51
  files.forEach((content, filename) => {
48
52
  const resource = ResourceUtil.createResource(filename, options.projectNamespace, content);
49
53
  writePromises.push(workspace.write(resource));
@@ -0,0 +1,3 @@
1
+ export default class AuthenticationError extends Error {
2
+ constructor(details?: string);
3
+ }
@@ -0,0 +1,8 @@
1
+ export default class AuthenticationError extends Error {
2
+ constructor(details) {
3
+ super(details);
4
+ this.name = "AuthenticationError";
5
+ this.message = "Authentication error. Use 'cf login' to authenticate in Cloud Foundry" + (details ? `: ${details}` : ".");
6
+ }
7
+ }
8
+ //# sourceMappingURL=authenticationError.js.map
@@ -85,6 +85,18 @@ export interface IHTML5RepoInfo {
85
85
  token: string;
86
86
  baseUri: string;
87
87
  }
88
+ export interface IReuseLibInfo {
89
+ name: string;
90
+ lazy: boolean;
91
+ html5AppHostId: string;
92
+ html5AppName: string;
93
+ html5AppVersion: string;
94
+ html5CacheBusterToken: string;
95
+ url: {
96
+ uri: string;
97
+ final: boolean;
98
+ };
99
+ }
88
100
  export interface IAuth {
89
101
  username: string;
90
102
  password: string;
@@ -0,0 +1,13 @@
1
+ import IProcessor from "./processors/processor.js";
2
+ export default class PreviewManager {
3
+ private readonly fetchLibsPromises;
4
+ static createFromRoot(appId: string, processor: IProcessor): Promise<PreviewManager>;
5
+ static isPreviewRequested(): boolean;
6
+ private constructor();
7
+ processPreviewResources(baseAppFiles: ReadonlyMap<string, string>): Promise<void>;
8
+ private preReadLibs;
9
+ private searchBaseAppXsAppJsonFile;
10
+ private mergeXsAppJsonFiles;
11
+ private static moveLibraryFiles;
12
+ private static modifyRoutes;
13
+ }
@@ -0,0 +1,142 @@
1
+ import { getLogger } from "@ui5/logger";
2
+ import ResourceUtil from "./util/resourceUtil.js";
3
+ import path from "path";
4
+ const log = getLogger("@ui5/task-adaptation::PreviewManager");
5
+ const REUSE_DIR = ".adp/reuse";
6
+ const APP_INFO_FILE = "ui5AppInfo.json";
7
+ const XS_APP_JSON_FILE = "xs-app.json";
8
+ export default class PreviewManager {
9
+ fetchLibsPromises = new Map();
10
+ static async createFromRoot(appId, processor) {
11
+ let ui5AppInfo = "";
12
+ if (PreviewManager.isPreviewRequested()) {
13
+ try {
14
+ ui5AppInfo = await ResourceUtil.readInProject(APP_INFO_FILE);
15
+ }
16
+ catch (_err) {
17
+ log.verbose("Preview mode not requested (env variable ADP_BUILDER_MODE=preview is not set), skipping preview resources processing.");
18
+ throw new Error(`ui5AppInfo.json is missing in project root, cannot process preview resources.`);
19
+ }
20
+ }
21
+ return new PreviewManager(appId, ui5AppInfo, processor);
22
+ }
23
+ static isPreviewRequested() {
24
+ return process.env.ADP_BUILDER_MODE === "preview";
25
+ }
26
+ constructor(appId, ui5AppInfo, processor) {
27
+ // If no ui5AppInfo is provided, no preview processing is needed
28
+ if (!ui5AppInfo) {
29
+ return;
30
+ }
31
+ const appInfo = JSON.parse(ui5AppInfo)[appId];
32
+ if (!appInfo) {
33
+ throw new Error(`No app info found for original app id '${appId}' in ui5AppInfo.json`);
34
+ }
35
+ const reuseLibs = appInfo.asyncHints.libs.filter(lib => lib.html5AppHostId && lib.html5AppName && lib.html5AppVersion);
36
+ if (reuseLibs.length > 0) {
37
+ this.fetchLibsPromises = this.preReadLibs(reuseLibs, processor);
38
+ }
39
+ const logMap = {
40
+ Error: log.error,
41
+ Warning: log.warning,
42
+ Info: log.info,
43
+ debug: log.verbose
44
+ };
45
+ appInfo.messages.forEach((message) => {
46
+ const logger = logMap[message.severity] || log.info;
47
+ logger(`ui5AppInfo.json: ${message.severity} Message: ${message.message} (Error Code: ${message.errorCode})`);
48
+ });
49
+ }
50
+ async processPreviewResources(baseAppFiles) {
51
+ log.verbose(`Downloading reuse libraries to reuse folder`);
52
+ const xsAppFiles = new Map();
53
+ if (this.fetchLibsPromises.size === 0) {
54
+ log.verbose("No reuse libraries defined in ui5AppInfo.json for preview");
55
+ return;
56
+ }
57
+ const mergedFiles = new Map();
58
+ for (const [libName, libFilesPromise] of this.fetchLibsPromises) {
59
+ const libFiles = await libFilesPromise;
60
+ if (!libFiles || libFiles.size === 0) {
61
+ log.warn(`No files found in reuse library ${libName} for preview`);
62
+ continue;
63
+ }
64
+ for (const [filename, content] of libFiles) {
65
+ mergedFiles.set(filename, content);
66
+ if (filename.includes(XS_APP_JSON_FILE)) {
67
+ xsAppFiles.set(filename, content);
68
+ }
69
+ }
70
+ }
71
+ this.searchBaseAppXsAppJsonFile(xsAppFiles, baseAppFiles);
72
+ this.mergeXsAppJsonFiles(xsAppFiles, mergedFiles);
73
+ await ResourceUtil.writeInProject(REUSE_DIR, mergedFiles);
74
+ }
75
+ preReadLibs(reuseLibs, processor) {
76
+ const promises = new Map();
77
+ reuseLibs.forEach(lib => {
78
+ log.info(`Downloading reuse library '${lib.html5AppName}' version '${lib.html5AppVersion}'`);
79
+ const promise = processor
80
+ .fetchReuseLib(lib.html5AppName, lib.html5CacheBusterToken, lib)
81
+ .then(libFiles => PreviewManager.moveLibraryFiles(libFiles, lib.html5AppName, lib.name));
82
+ promises.set(lib.html5AppName, promise);
83
+ });
84
+ return promises;
85
+ }
86
+ searchBaseAppXsAppJsonFile(xsAppFiles, baseAppFiles) {
87
+ const xsAppJsonContent = baseAppFiles.get(XS_APP_JSON_FILE);
88
+ if (xsAppJsonContent) {
89
+ xsAppFiles.set(XS_APP_JSON_FILE, xsAppJsonContent);
90
+ }
91
+ else {
92
+ log.warn("xs-app.json is missing in the downloaded base app files for preview");
93
+ }
94
+ }
95
+ mergeXsAppJsonFiles(xsAppFiles, files) {
96
+ // Start with empty xs-app.json
97
+ // welcomeFile is not needed for preview
98
+ const mergedXsAppJson = {
99
+ authenticationMethod: "none",
100
+ routes: []
101
+ };
102
+ xsAppFiles.forEach((xsAppInfoContent) => {
103
+ const xsAppInfo = JSON.parse(xsAppInfoContent);
104
+ if (mergedXsAppJson.authenticationMethod === "none" && xsAppInfo.authenticationMethod) {
105
+ mergedXsAppJson.authenticationMethod = xsAppInfo.authenticationMethod;
106
+ }
107
+ if (Array.isArray(xsAppInfo.routes)) {
108
+ mergedXsAppJson.routes = mergedXsAppJson.routes.concat(xsAppInfo.routes);
109
+ }
110
+ });
111
+ files.set(XS_APP_JSON_FILE, JSON.stringify(mergedXsAppJson, null, 2));
112
+ }
113
+ static moveLibraryFiles(inputFiles, libraryName, libId) {
114
+ const files = new Map();
115
+ inputFiles.forEach((content, filename) => {
116
+ const newFilename = path.join(libraryName, filename);
117
+ // Source path in xs-app.json needs to be adjusted to new location
118
+ if (filename.includes(XS_APP_JSON_FILE)) {
119
+ content = PreviewManager.modifyRoutes(content, libraryName, libId);
120
+ }
121
+ files.set(newFilename, content);
122
+ });
123
+ return files;
124
+ }
125
+ ;
126
+ static modifyRoutes(xsAppJson, libName, libId) {
127
+ const xsApp = JSON.parse(xsAppJson);
128
+ xsApp.routes = xsApp.routes.map((route) => {
129
+ route.source = route.source.replace(new RegExp("^\\^\\/?(resources/)?"), `^/resources/${libId.replaceAll(".", "/")}/`);
130
+ if (route.service === "html5-apps-repo-rt") {
131
+ route = {
132
+ source: route.source,
133
+ target: route.target,
134
+ localDir: path.join(REUSE_DIR, libName)
135
+ };
136
+ }
137
+ return route;
138
+ });
139
+ return JSON.stringify(xsApp);
140
+ }
141
+ }
142
+ //# sourceMappingURL=previewManager.js.map
@@ -10,6 +10,7 @@ export default class AbapProcessor implements IProcessor {
10
10
  constructor(configuration: IConfiguration, abapRepoManager: AbapRepoManager, annotationManager: AnnotationManager);
11
11
  getAppVariantIdHierarchy(appId: string): Promise<IAppVariantIdHierarchyItem[]>;
12
12
  fetch(repoName: string, _cachebusterToken: string): Promise<Map<string, string>>;
13
+ fetchReuseLib(): Promise<Map<string, string>>;
13
14
  validateConfiguration(): void;
14
15
  updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>, appVariantId: string, prefix: string): Promise<void>;
15
16
  getConfigurationType(): string;
@@ -22,6 +22,9 @@ export default class AbapProcessor {
22
22
  fetch(repoName, _cachebusterToken) {
23
23
  return this.abapRepoManager.fetch(repoName);
24
24
  }
25
+ fetchReuseLib() {
26
+ throw new Error("Preview is not available on SAP S/4HANA On-Premise or Cloud Systems. Please create a ticket on CA-UI5-FL-ADP-BAS component.");
27
+ }
25
28
  validateConfiguration() {
26
29
  // validate general app config
27
30
  const properties = ["appName"];
@@ -1,11 +1,12 @@
1
1
  import IAppInfo from "../model/appVariantIdHierarchyItem.js";
2
- import { IConfiguration } from "../model/types.js";
2
+ import { IConfiguration, IReuseLibInfo } from "../model/types.js";
3
3
  import IProcessor from "./processor.js";
4
4
  export default class CFProcessor implements IProcessor {
5
5
  private configuration;
6
6
  constructor(configuration: IConfiguration);
7
7
  getAppVariantIdHierarchy(appId: string): Promise<IAppInfo[]>;
8
8
  fetch(_repoName: string, _cachebusterToken: string): Promise<Map<string, string>>;
9
+ fetchReuseLib(_libName: string, _cachebusterToken: string, lib: IReuseLibInfo): Promise<Map<string, string>>;
9
10
  validateConfiguration(): void;
10
11
  updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>): Promise<void>;
11
12
  private updateXsAppJson;
@@ -9,6 +9,7 @@ import { cached } from "../cache/cacheHolder.js";
9
9
  import { validateObject } from "../util/commonUtil.js";
10
10
  import CFUtil from "../util/cfUtil.js";
11
11
  import { getLogger } from "@ui5/logger";
12
+ import PreviewManager from "../previewManager.js";
12
13
  const log = getLogger("@ui5/task-adaptation::CFProcessor");
13
14
  export default class CFProcessor {
14
15
  configuration;
@@ -26,12 +27,18 @@ export default class CFProcessor {
26
27
  fetch(_repoName, _cachebusterToken) {
27
28
  return HTML5RepoManager.getBaseAppFiles(this.configuration);
28
29
  }
30
+ fetchReuseLib(_libName, _cachebusterToken, lib) {
31
+ return HTML5RepoManager.getReuseLibFiles(this.configuration, lib);
32
+ }
29
33
  validateConfiguration() {
30
34
  validateObject(this.configuration, ["appHostId", "appName", "appVersion"], "should be specified in ui5.yaml configuration");
31
35
  }
32
36
  async updateLandscapeSpecificContent(baseAppManifest, baseAppFiles) {
33
37
  this.updateCloudPlatform(baseAppManifest);
34
- await this.updateXsAppJson(baseAppFiles);
38
+ // Preview uses destinations and does not require xs-app.json updates
39
+ if (!PreviewManager.isPreviewRequested()) {
40
+ await this.updateXsAppJson(baseAppFiles);
41
+ }
35
42
  }
36
43
  async updateXsAppJson(baseAppFiles) {
37
44
  const xsAppJsonContent = baseAppFiles.get("xs-app.json");
@@ -119,4 +126,7 @@ export default class CFProcessor {
119
126
  __decorate([
120
127
  cached()
121
128
  ], CFProcessor.prototype, "fetch", null);
129
+ __decorate([
130
+ cached()
131
+ ], CFProcessor.prototype, "fetchReuseLib", null);
122
132
  //# sourceMappingURL=cfProcessor.js.map
@@ -1,8 +1,9 @@
1
1
  import IAppVariantIdHierarchyItem from "../model/appVariantIdHierarchyItem.js";
2
- import { IConfiguration } from "../model/types.js";
2
+ import { IConfiguration, IReuseLibInfo } from "../model/types.js";
3
3
  export default interface IProcessor {
4
4
  getAppVariantIdHierarchy(appId: string): Promise<IAppVariantIdHierarchyItem[]>;
5
5
  fetch(repoName: string, cachebusterToken: string): Promise<Map<string, string>>;
6
+ fetchReuseLib(repoName: string, cachebusterToken: string, lib: IReuseLibInfo): Promise<Map<string, string>>;
6
7
  createAppVariantHierarchyItem(appVariantId: string, version: string): void;
7
8
  updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: Map<string, string>, appVariantId: string, prefix: string): Promise<void>;
8
9
  }
@@ -1,11 +1,12 @@
1
- import { IConfiguration } from "./../model/types.js";
1
+ import { IConfiguration, IReuseLibInfo } from "./../model/types.js";
2
2
  export default class HTML5RepoManager {
3
3
  static getBaseAppFiles(configuration: IConfiguration): Promise<Map<string, string>>;
4
4
  static getMetadata(configuration: IConfiguration): Promise<any>;
5
+ static getReuseLibFiles(configuration: IConfiguration, lib: IReuseLibInfo): Promise<Map<string, string>>;
5
6
  private static getHtml5RepoInfo;
6
7
  private static getHTML5Credentials;
7
8
  private static getToken;
8
9
  private static requestMetadata;
9
- private static getBaseAppZipEntries;
10
+ private static getAppZipEntries;
10
11
  private static download;
11
12
  }
@@ -6,12 +6,26 @@ const log = getLogger("@ui5/task-adaptation::HTML5RepoManager");
6
6
  export default class HTML5RepoManager {
7
7
  static async getBaseAppFiles(configuration) {
8
8
  const { token, baseUri } = await this.getHtml5RepoInfo(configuration);
9
- return this.getBaseAppZipEntries(configuration, baseUri, token);
9
+ const app = {
10
+ appName: configuration.appName,
11
+ appVersion: configuration.appVersion,
12
+ appHostId: configuration.appHostId
13
+ };
14
+ return this.getAppZipEntries(app, baseUri, token);
10
15
  }
11
16
  static async getMetadata(configuration) {
12
17
  const { token, baseUri } = await this.getHtml5RepoInfo(configuration);
13
18
  return this.requestMetadata(configuration, baseUri, token);
14
19
  }
20
+ static async getReuseLibFiles(configuration, lib) {
21
+ const { token, baseUri } = await this.getHtml5RepoInfo(configuration);
22
+ const libAppData = {
23
+ appName: lib.html5AppName,
24
+ appVersion: lib.html5AppVersion,
25
+ appHostId: lib.html5AppHostId
26
+ };
27
+ return this.getAppZipEntries(libAppData, baseUri, token);
28
+ }
15
29
  static async getHtml5RepoInfo(configuration) {
16
30
  const spaceGuid = await CFUtil.getSpaceGuid(configuration?.space);
17
31
  const credentials = await this.getHTML5Credentials(spaceGuid);
@@ -65,10 +79,9 @@ export default class HTML5RepoManager {
65
79
  const metadata = await RequestUtil.get(uri, requestOptions);
66
80
  return metadata.find((item) => item.appHostId === appHostId && item.applicationName === appName && item.applicationVersion === appVersion);
67
81
  }
68
- static async getBaseAppZipEntries(options, html5RepoBaseUri, token) {
69
- const { appHostId, appName, appVersion } = options;
70
- const uri = `${html5RepoBaseUri}/applications/content/${appName}-${appVersion}/`;
71
- const zip = await this.download(token, appHostId, uri);
82
+ static async getAppZipEntries(app, html5RepoBaseUri, token) {
83
+ const uri = `${html5RepoBaseUri}/applications/content/${app.appName}-${app.appVersion}/`;
84
+ const zip = await this.download(token, app.appHostId, uri);
72
85
  return unzipZipEntries(zip);
73
86
  }
74
87
  static async download(token, appHostId, uri) {
@@ -15,7 +15,7 @@ export default class CFUtil {
15
15
  private static createServiceKey;
16
16
  private static deleteServiceKeyUnsafe;
17
17
  private static getServiceInstance;
18
- static processErrors(json: any): void;
18
+ static processCfErrors(errors?: ICfError[]): void;
19
19
  static requestCfApi(url: string): Promise<IResource[]>;
20
20
  static getOAuthToken(): Promise<string>;
21
21
  private static cfExecute;
@@ -76,3 +76,10 @@ export default class CFUtil {
76
76
  */
77
77
  static getSpaceGuid(spaceGuid?: string): Promise<string>;
78
78
  }
79
+ interface ICfError {
80
+ code: number;
81
+ title: string;
82
+ detail: string;
83
+ [key: string]: any;
84
+ }
85
+ export {};
@@ -3,6 +3,7 @@ import { getSpaceGuidThrowIfUndefined } from "@sap/cf-tools/out/src/utils.js";
3
3
  import { Cli } from "@sap/cf-tools/out/src/cli.js";
4
4
  import { eFilters } from "@sap/cf-tools/out/src/types.js";
5
5
  import { getLogger } from "@ui5/logger";
6
+ import AuthenticationError from "../model/authenticationError.js";
6
7
  const log = getLogger("@ui5/task-adaptation::CFUtil");
7
8
  export default class CFUtil {
8
9
  /**
@@ -103,19 +104,20 @@ export default class CFUtil {
103
104
  guid: service.guid
104
105
  }));
105
106
  }
106
- static processErrors(json) {
107
- if (json?.errors?.length > 0) {
108
- const message = JSON.stringify(json.errors);
109
- if (json?.errors?.some((e) => e.title === "CF-NotAuthenticated" || e.code === 10002)) {
110
- throw new Error(`Authentication error. Use 'cf login' to authenticate in Cloud Foundry: ${message}`);
111
- }
112
- throw new Error(`Failed sending request to Cloud Foundry: ${message}`);
107
+ static processCfErrors(errors) {
108
+ if (!errors || errors.length === 0) {
109
+ return;
110
+ }
111
+ const authError = errors.find(e => e.title === "CF-NotAuthenticated" || e.code === 10002);
112
+ if (authError) {
113
+ throw new AuthenticationError(authError.detail);
113
114
  }
115
+ throw new Error(`Failed sending request to Cloud Foundry: ${JSON.stringify(errors)}`);
114
116
  }
115
117
  static async requestCfApi(url) {
116
118
  const response = await this.cfExecute(["curl", url]);
117
119
  const json = this.parseJson(response);
118
- this.processErrors(json);
120
+ this.processCfErrors(json?.errors);
119
121
  const resources = json?.resources;
120
122
  const totalPages = json?.pagination?.total_pages;
121
123
  if (totalPages > 1) {
@@ -142,11 +144,17 @@ export default class CFUtil {
142
144
  if (errorValues?.length > 0) {
143
145
  log.verbose(this.errorsToString(errorValues));
144
146
  }
147
+ if (response.stdout === "\n") {
148
+ throw new AuthenticationError();
149
+ }
145
150
  return response.stdout;
146
151
  }
147
152
  errors.add(response.error || response.stderr);
148
153
  }
149
154
  catch (error) {
155
+ if (error instanceof AuthenticationError) {
156
+ throw error;
157
+ }
150
158
  errors.add(error.message);
151
159
  }
152
160
  }
@@ -0,0 +1,8 @@
1
+ export declare const moveFiles: (inputFiles: ReadonlyMap<string, string>, prefix: string, id: string) => {
2
+ files: Map<string, string>;
3
+ renamingPaths: Map<string, string>;
4
+ };
5
+ export declare const moveFile: (filename: string, content: string, prefix: string, id: string) => {
6
+ newFilename: string;
7
+ renamingPath: Map<string, string>;
8
+ };
@@ -0,0 +1,77 @@
1
+ import path from "path";
2
+ import { isManifestChange } from "../commonUtil.js";
3
+ const EXT_DIR = "ext/";
4
+ const CHANGES_DIR = "changes/";
5
+ const nameSpaceRegex = new RegExp(`(?<=ControllerExtension.extend\\(")([^"]*)(?=")`);
6
+ /**
7
+ * 2p. We move to appVariantFolder (prefix) only files that go along the
8
+ * change files, like js or fragments. Changes are renamed in resources,
9
+ * packed in flexibility-bundle and removed.
10
+ * @param filename - The filename relative to the root
11
+ */
12
+ function shouldMove(filename, content) {
13
+ //TODO: is it more reliable to check change/fileType?
14
+ if (isManifestChange(filename, content)) {
15
+ return true;
16
+ }
17
+ return filename.startsWith(CHANGES_DIR) && [".change", ".variant", ".ctrl_variant", ".ctrl_variant_change", ".ctrl_variant_management_change"].every(ext => !filename.endsWith(ext))
18
+ || filename.startsWith(EXT_DIR);
19
+ }
20
+ /**
21
+ * For controller extension the namespace needs a prefix
22
+ * The namespace needs to be unique for clear identification
23
+ * If controller extension have the same namespace the last one will be used
24
+ * @param filename - The filename relative to the root
25
+ * @returns
26
+ */
27
+ function shouldNamespaceRenamed(filename) {
28
+ return filename.startsWith(CHANGES_DIR) && filename.endsWith(".js");
29
+ }
30
+ /**
31
+ * Returns path without first directory (which is usually "changes" or
32
+ * "ext") and without file extension. For example, for
33
+ * "changes/coding/FixingDay.js" it returns "coding/FixingDay", for
34
+ * "changes/customer_app_variant5/fragments/hello_world_fixing_day.fragment.xml"
35
+ * it returns
36
+ * "changes/customer_app_variant5/fragments/hello_world_fixing_day". We
37
+ * remove all extensions not to rename it with slashes instead of dots. And
38
+ * we also exclude "changes" or "ext" include the prefix after them and then
39
+ * the filename. Without 'changes' or 'ext' directory, we need to replace
40
+ * only rest: coding/FixingDay.js to app_var_id1/coding/FixingDay.js
41
+ * @param filename
42
+ * @returns
43
+ */
44
+ function getPathWithoutExtensions(filename) {
45
+ const [_dir, ...rest] = filename.split("/");
46
+ const restWOExt = [...rest];
47
+ restWOExt[restWOExt.length - 1] = restWOExt[restWOExt.length - 1].split(".")[0];
48
+ return restWOExt.join("/");
49
+ }
50
+ export const moveFiles = (inputFiles, prefix, id) => {
51
+ const files = new Map();
52
+ let renamingPaths = new Map();
53
+ inputFiles.forEach((content, filename) => {
54
+ const { newFilename, renamingPath } = moveFile(filename, content, prefix, id);
55
+ files.set(newFilename, content);
56
+ renamingPaths = new Map([...renamingPaths, ...renamingPath]);
57
+ });
58
+ return { files, renamingPaths };
59
+ };
60
+ export const moveFile = (filename, content, prefix, id) => {
61
+ let newFilename = filename;
62
+ let renamingPath = new Map();
63
+ if (shouldMove(filename, content)) {
64
+ const [dir, ...rest] = filename.split("/");
65
+ newFilename = path.join(dir, prefix, rest.join("/"));
66
+ const restWOExtPath = getPathWithoutExtensions(filename);
67
+ renamingPath.set(restWOExtPath, path.join(prefix, restWOExtPath));
68
+ }
69
+ if (shouldNamespaceRenamed(filename)) {
70
+ const namespaceMatch = nameSpaceRegex.exec(content.trim());
71
+ const controllerExtensionPath = namespaceMatch ? namespaceMatch[0] : "";
72
+ const fileName = controllerExtensionPath.replace(id, "");
73
+ renamingPath.set(controllerExtensionPath, `${id}.${prefix}${fileName}`);
74
+ }
75
+ return { newFilename, renamingPath };
76
+ };
77
+ //# sourceMappingURL=changeFileMoveHandler.js.map
@@ -3,11 +3,19 @@ export default class ResourceUtil {
3
3
  static relativeToRoot(resourcePath: string, projectNamespace?: string): string;
4
4
  static getResourcePath(projectNamespace?: string, ...paths: string[]): string;
5
5
  static write(dir: string, files: Map<string, string>): Promise<void[]>;
6
- private static _read;
7
- static read(folder: string): Map<string, string>;
6
+ static writeInProject(dir: string, files: Map<string, string>): Promise<void[]>;
7
+ static byGlob(dir: string, pattern: string, excludes?: string[]): Promise<Map<string, string>>;
8
+ static byGlobInProject(pattern: string, excludes?: string[]): Promise<Map<string, string>>;
9
+ static readInProject(filepath: string): Promise<string>;
8
10
  static getString(resource: any): Promise<string>;
9
11
  static getJson(resource: any): Promise<any>;
10
12
  static setString(resource: any, str: string): void;
13
+ /**
14
+ * Check whether a file or directory exists (non-throwing).
15
+ * @param filePath Absolute or relative path
16
+ * @returns true if the path exists, false if not
17
+ */
18
+ private static fileExists;
11
19
  static createResource(filename: string, projectNamespace: string, content: string): any;
12
- static toFileMap(resources: ReadonlyArray<Resource>, projectNamespace: string): Promise<Map<string, string>>;
20
+ static toFileMap(resources: ReadonlyArray<Resource>, projectNamespace?: string): Promise<Map<string, string>>;
13
21
  }