@ui5/task-adaptation 1.4.3 → 1.5.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/CHANGELOG.md +6 -2
- package/README.md +16 -12
- package/dist/annotationManager.d.ts +1 -1
- package/dist/annotationManager.js +5 -8
- package/dist/annotations/serviceRequestor.js +3 -15
- package/dist/appVariantManager.d.ts +16 -11
- package/dist/appVariantManager.js +56 -83
- package/dist/baseAppManager.d.ts +16 -16
- package/dist/baseAppManager.js +57 -80
- package/dist/bundle.js +58 -42
- package/dist/cache/cacheHolder.d.ts +18 -0
- package/dist/cache/cacheHolder.js +80 -0
- package/dist/i18nManager.js +11 -9
- package/dist/index.js +34 -9
- package/dist/model/appVariantIdHierarchyItem.d.ts +5 -0
- package/dist/model/appVariantIdHierarchyItem.js +2 -0
- package/dist/model/language.d.ts +3 -3
- package/dist/model/language.js +11 -4
- package/dist/model/types.d.ts +2 -0
- package/dist/processors/abapProcessor.d.ts +5 -5
- package/dist/processors/abapProcessor.js +19 -7
- package/dist/processors/cfProcessor.d.ts +4 -4
- package/dist/processors/cfProcessor.js +28 -12
- package/dist/processors/processor.d.ts +4 -2
- package/dist/processors/processor.js +2 -4
- package/dist/repositories/abapRepoManager.d.ts +3 -1
- package/dist/repositories/abapRepoManager.js +24 -5
- package/dist/util/cfUtil.js +1 -1
- package/dist/util/commonUtil.d.ts +4 -2
- package/dist/util/commonUtil.js +105 -32
- package/dist/util/filesUtil.d.ts +17 -0
- package/dist/util/filesUtil.js +49 -0
- package/dist/util/i18nMerger.d.ts +15 -20
- package/dist/util/i18nMerger.js +46 -64
- package/dist/util/renamingHandlers/manifestHandler.d.ts +6 -0
- package/dist/util/renamingHandlers/manifestHandler.js +20 -0
- package/dist/util/renamingHandlers/renamingHandler.d.ts +4 -0
- package/dist/util/renamingHandlers/renamingHandler.js +2 -0
- package/dist/util/resourceUtil.d.ts +3 -1
- package/dist/util/resourceUtil.js +15 -2
- package/eslint.config.js +2 -5
- package/package.json +18 -12
- package/scripts/test-integration-prep.sh +4 -0
- package/types/ui5.d.ts +10 -0
- package/dist/cache/annotationsCacheManager.d.ts +0 -8
- package/dist/cache/annotationsCacheManager.js +0 -16
- package/dist/cache/baseAppFilesCacheManager.d.ts +0 -6
- package/dist/cache/baseAppFilesCacheManager.js +0 -12
- package/dist/cache/cacheManager.d.ts +0 -16
- package/dist/cache/cacheManager.js +0 -65
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
1
7
|
import HTML5RepoManager from "../repositories/html5RepoManager.js";
|
|
8
|
+
import { cached } from "../cache/cacheHolder.js";
|
|
2
9
|
import { validateObject } from "../util/commonUtil.js";
|
|
3
10
|
export default class CFProcessor {
|
|
4
11
|
configuration;
|
|
5
|
-
|
|
6
|
-
constructor(configuration, cacheManager) {
|
|
12
|
+
constructor(configuration) {
|
|
7
13
|
this.configuration = configuration;
|
|
8
|
-
this.cacheManager = cacheManager;
|
|
9
14
|
}
|
|
10
|
-
async
|
|
11
|
-
|
|
15
|
+
async getAppVariantIdHierarchy(appId) {
|
|
16
|
+
const metadata = await HTML5RepoManager.getMetadata(this.configuration);
|
|
17
|
+
return [{
|
|
18
|
+
repoName: this.configuration.appName,
|
|
19
|
+
appVariantId: appId,
|
|
20
|
+
cachebusterToken: metadata.changedOn
|
|
21
|
+
}];
|
|
22
|
+
}
|
|
23
|
+
fetch(_repoName, _cachebusterToken) {
|
|
24
|
+
return HTML5RepoManager.getBaseAppFiles(this.configuration);
|
|
12
25
|
}
|
|
13
26
|
validateConfiguration() {
|
|
14
27
|
validateObject(this.configuration, ["appHostId", "appName", "appVersion"], "should be specified in ui5.yaml configuration");
|
|
@@ -16,20 +29,20 @@ export default class CFProcessor {
|
|
|
16
29
|
async updateLandscapeSpecificContent(renamedBaseAppManifest) {
|
|
17
30
|
this.updateCloudPlatform(renamedBaseAppManifest);
|
|
18
31
|
}
|
|
19
|
-
updateCloudPlatform(
|
|
20
|
-
const sapCloudService =
|
|
21
|
-
const sapPlatformCf =
|
|
32
|
+
updateCloudPlatform(baseAppManifest) {
|
|
33
|
+
const sapCloudService = baseAppManifest["sap.cloud"]?.service;
|
|
34
|
+
const sapPlatformCf = baseAppManifest["sap.platform.cf"];
|
|
22
35
|
if (sapPlatformCf?.oAuthScopes && sapCloudService) {
|
|
23
36
|
sapPlatformCf.oAuthScopes = sapPlatformCf.oAuthScopes.map((scope) => scope.replace(`$XSAPPNAME.`, `$XSAPPNAME('${sapCloudService}').`));
|
|
24
37
|
}
|
|
25
38
|
if (this.configuration.sapCloudService) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
39
|
+
if (baseAppManifest["sap.cloud"] == null) {
|
|
40
|
+
baseAppManifest["sap.cloud"] = {};
|
|
28
41
|
}
|
|
29
|
-
|
|
42
|
+
baseAppManifest["sap.cloud"].service = this.configuration.sapCloudService;
|
|
30
43
|
}
|
|
31
44
|
else {
|
|
32
|
-
delete
|
|
45
|
+
delete baseAppManifest["sap.cloud"];
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
getConfigurationType() {
|
|
@@ -42,4 +55,7 @@ export default class CFProcessor {
|
|
|
42
55
|
};
|
|
43
56
|
}
|
|
44
57
|
}
|
|
58
|
+
__decorate([
|
|
59
|
+
cached()
|
|
60
|
+
], CFProcessor.prototype, "fetch", null);
|
|
45
61
|
//# sourceMappingURL=cfProcessor.js.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import IAppVariantIdHierarchyItem from "../model/appVariantIdHierarchyItem.js";
|
|
1
2
|
import { IConfiguration } from "../model/types.js";
|
|
2
3
|
export default interface IProcessor {
|
|
4
|
+
getAppVariantIdHierarchy(appId: string): Promise<IAppVariantIdHierarchyItem[]>;
|
|
5
|
+
fetch(repoName: string, cachebusterToken: string): Promise<Map<string, string>>;
|
|
3
6
|
createAppVariantHierarchyItem(appVariantId: string, version: string): void;
|
|
4
|
-
|
|
5
|
-
updateLandscapeSpecificContent(renamedBaseAppManifest: any, baseAppFiles?: Map<string, string>): Promise<void>;
|
|
7
|
+
updateLandscapeSpecificContent(baseAppManifest: any, baseAppFiles: ReadonlyMap<string, string>, appVariantId: string): Promise<void>;
|
|
6
8
|
}
|
|
7
9
|
export declare function determineProcessor(configuration: IConfiguration): IProcessor;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import AbapProcessor from "./abapProcessor.js";
|
|
2
2
|
import AbapRepoManager from "../repositories/abapRepoManager.js";
|
|
3
3
|
import AnnotationManager from "../annotationManager.js";
|
|
4
|
-
import BaseAppFilesCacheManager from "../cache/baseAppFilesCacheManager.js";
|
|
5
4
|
import CFProcessor from "./cfProcessor.js";
|
|
6
5
|
export function determineProcessor(configuration) {
|
|
7
|
-
const cacheManager = new BaseAppFilesCacheManager(configuration);
|
|
8
6
|
const abapRepoManager = new AbapRepoManager(configuration);
|
|
9
7
|
const annotationManager = new AnnotationManager(configuration, abapRepoManager);
|
|
10
8
|
const processors = [
|
|
11
|
-
new CFProcessor(configuration
|
|
12
|
-
new AbapProcessor(configuration,
|
|
9
|
+
new CFProcessor(configuration),
|
|
10
|
+
new AbapProcessor(configuration, abapRepoManager, annotationManager)
|
|
13
11
|
];
|
|
14
12
|
let processor = processors.find(processor => processor.getConfigurationType() === configuration.type);
|
|
15
13
|
if (processor) {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { IConfiguration, IMetadata } from "../model/types.js";
|
|
2
2
|
import AbapProvider from "./abapProvider.js";
|
|
3
|
+
import IAppVariantIdHierarchyItem from "../model/appVariantIdHierarchyItem.js";
|
|
3
4
|
export default class AbapRepoManager {
|
|
4
5
|
private configuration;
|
|
5
6
|
private abapProvider;
|
|
6
7
|
constructor(configuration: IConfiguration, abapProvider?: AbapProvider);
|
|
8
|
+
getAppVariantIdHierarchy(id: string): Promise<IAppVariantIdHierarchyItem[]>;
|
|
7
9
|
getAnnotationMetadata(uri: string): Promise<IMetadata>;
|
|
8
10
|
downloadAnnotationFile(uri: string): Promise<Map<string, any>>;
|
|
9
11
|
getMetadata(id: string): Promise<IMetadata>;
|
|
10
|
-
|
|
12
|
+
fetch(repoName: string): Promise<Map<string, string>>;
|
|
11
13
|
}
|
|
@@ -20,6 +20,26 @@ export default class AbapRepoManager {
|
|
|
20
20
|
this.configuration = configuration;
|
|
21
21
|
this.abapProvider = abapProvider ? abapProvider : new AbapProvider();
|
|
22
22
|
}
|
|
23
|
+
async getAppVariantIdHierarchy(id) {
|
|
24
|
+
const provider = await this.abapProvider.get(this.configuration);
|
|
25
|
+
const lrep = provider.getLayeredRepository();
|
|
26
|
+
const response = await lrep.get("/dta_folder/app_info", {
|
|
27
|
+
params: { id }
|
|
28
|
+
});
|
|
29
|
+
if (response.status === 200) {
|
|
30
|
+
return JSON.parse(response.data)?.appVariantIdHierarchy;
|
|
31
|
+
}
|
|
32
|
+
else if (this.configuration.appName) {
|
|
33
|
+
// Fallback to old API on old ABAP backend or CF for backward compatibility
|
|
34
|
+
const metadataResponse = await this.getMetadata(id);
|
|
35
|
+
return [{
|
|
36
|
+
repoName: this.configuration.appName,
|
|
37
|
+
appVariantId: id,
|
|
38
|
+
cachebusterToken: metadataResponse.changedOn
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`App variant id hierarchy for app id '${id}' is not provided`);
|
|
42
|
+
}
|
|
23
43
|
async getAnnotationMetadata(uri) {
|
|
24
44
|
const provider = await this.abapProvider.get(this.configuration);
|
|
25
45
|
const response = await provider.head(uri);
|
|
@@ -48,12 +68,11 @@ export default class AbapRepoManager {
|
|
|
48
68
|
id
|
|
49
69
|
};
|
|
50
70
|
}
|
|
51
|
-
async
|
|
52
|
-
const
|
|
53
|
-
const encodedAppName = encodeURIComponent(appName);
|
|
71
|
+
async fetch(repoName) {
|
|
72
|
+
const encodedRepoName = encodeURIComponent(repoName);
|
|
54
73
|
const provider = await this.abapProvider.get(this.configuration);
|
|
55
74
|
const ui5Repo = provider.getUi5AbapRepository();
|
|
56
|
-
const response = await ui5Repo.get(`/Repositories('${
|
|
75
|
+
const response = await ui5Repo.get(`/Repositories('${encodedRepoName}')`, {
|
|
57
76
|
params: {
|
|
58
77
|
DownloadFiles: "RUNTIME",
|
|
59
78
|
CodePage: "UTF8",
|
|
@@ -65,7 +84,7 @@ export default class AbapRepoManager {
|
|
|
65
84
|
const buffer = Buffer.from(data.d.ZipArchive, "base64");
|
|
66
85
|
return unzipZipEntries(buffer);
|
|
67
86
|
}
|
|
68
|
-
throw new Error(`App '${
|
|
87
|
+
throw new Error(`App '${repoName}' doesn't contain files`);
|
|
69
88
|
}
|
|
70
89
|
}
|
|
71
90
|
//# sourceMappingURL=abapRepoManager.js.map
|
package/dist/util/cfUtil.js
CHANGED
|
@@ -78,7 +78,7 @@ export default class CFUtil {
|
|
|
78
78
|
return this.cfExecute(["create-service-key", serviceInstanceName, serviceKeyName]);
|
|
79
79
|
}
|
|
80
80
|
catch (error) {
|
|
81
|
-
throw new Error(`Couldn't create a service key for instance ${serviceInstanceName}: ${error
|
|
81
|
+
throw new Error(`Couldn't create a service key for instance: ${serviceInstanceName}: ${error}`);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
static async getServiceInstance(params) {
|
|
@@ -3,11 +3,13 @@ import Language from "../model/language.js";
|
|
|
3
3
|
export declare function dotToUnderscore(value: string): string;
|
|
4
4
|
export declare function validateObject<T extends object>(options: T, properties: Array<keyof T>, message: string): void;
|
|
5
5
|
export declare function escapeRegex(update: string): string;
|
|
6
|
-
export declare function renameResources(files:
|
|
6
|
+
export declare function renameResources(files: ReadonlyMap<string, string>, search: string[], replacement: string, ignoreInStrings?: string[]): Map<string, string>;
|
|
7
|
+
export declare function rename(content: string, searchTerms: string[], replacement: string, ignoreInStrings?: string[]): string;
|
|
7
8
|
export declare function insertInArray<T>(array: T[], index: number, insert: T): void;
|
|
8
9
|
export declare function writeTempAnnotations({ writeTempFiles }: IConfiguration, name: string, language: Language, content: string): void;
|
|
9
|
-
export declare function
|
|
10
|
+
export declare function trimExtension(filePath: string): string;
|
|
10
11
|
export declare function traverse(json: any, paths: string[], callback: (json: any, key: string | number, paths: string[]) => void): void;
|
|
11
12
|
export declare function logBuilderVersion(): void;
|
|
12
13
|
export declare function logBetaUsage(): void;
|
|
13
14
|
export declare function getUniqueName(existingNames: string[], template: string): string;
|
|
15
|
+
export declare function getI18nPropertyKeys(files: ReadonlyMap<string, string>): string[];
|
package/dist/util/commonUtil.js
CHANGED
|
@@ -17,38 +17,96 @@ export function validateObject(options, properties, message) {
|
|
|
17
17
|
export function escapeRegex(update) {
|
|
18
18
|
return update.replaceAll(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
19
19
|
}
|
|
20
|
-
export function renameResources(files, search, replacement) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const [before] = replacement.split(search);
|
|
27
|
-
// Matches a position in the string that is not immediately preceded by
|
|
28
|
-
// the string "before". Since we won't replace anyway, we should also
|
|
29
|
-
// ignore one with the slashes.
|
|
30
|
-
const escapedBefore = escapeRegex(before).replaceAll("\\.", "[\\./]");
|
|
31
|
-
escapedSearch = `(?<!${escapedBefore})${escapeRegex(search)}`;
|
|
20
|
+
export function renameResources(files, search, replacement, ignoreInStrings = []) {
|
|
21
|
+
return new Map([...files].map(([filepath, content]) => [filepath, rename(content, search, replacement, ignoreInStrings)]));
|
|
22
|
+
}
|
|
23
|
+
export function rename(content, searchTerms, replacement, ignoreInStrings = []) {
|
|
24
|
+
if (replacement.includes(".") && !searchTerms.some(searchTerm => searchTerm.includes("."))) {
|
|
25
|
+
throw new Error("Ambiguous reference and appVariantId: both should contains dots or both should not contain dots.");
|
|
32
26
|
}
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
if (!content || !searchTerms || searchTerms.length === 0) {
|
|
28
|
+
return content;
|
|
35
29
|
}
|
|
36
|
-
const dotToSlash = (
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
const dotToSlash = (str) => str.replaceAll(".", "\/");
|
|
31
|
+
const replacementSlash = dotToSlash(replacement);
|
|
32
|
+
// We don't want to replace in adaptation project ids
|
|
33
|
+
ignoreInStrings.push(replacement);
|
|
34
|
+
ignoreInStrings.push(replacementSlash);
|
|
35
|
+
let start = 0;
|
|
36
|
+
while (true) {
|
|
37
|
+
// If we don't replace some strings in the content - we find all of them
|
|
38
|
+
// and then don't replace inside their start and end indices.
|
|
39
|
+
const ignoredStrings = ignoreInStrings.map(string => {
|
|
40
|
+
return findAllOccurrences(content, string, start).map(i => ({ start: i, end: i + string.length }));
|
|
41
|
+
}).filter(arr => arr.length > 0) || [];
|
|
42
|
+
// We find the next search index with dots and slashes. Then we replace
|
|
43
|
+
// the nearest one and start search again in the next loop step.
|
|
44
|
+
const indices = new Array();
|
|
45
|
+
for (const searchTerm of searchTerms) {
|
|
46
|
+
const searchTermSlash = dotToSlash(searchTerm);
|
|
47
|
+
indices.push({
|
|
48
|
+
i: content.indexOf(searchTerm, start),
|
|
49
|
+
replacement,
|
|
50
|
+
searchTerm
|
|
51
|
+
});
|
|
52
|
+
indices.push({
|
|
53
|
+
i: content.indexOf(searchTermSlash, start),
|
|
54
|
+
replacement: replacementSlash,
|
|
55
|
+
searchTerm: searchTermSlash
|
|
56
|
+
});
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
const found = indices.filter(({ i }) => i > -1);
|
|
59
|
+
if (found.length === 0) {
|
|
60
|
+
return content;
|
|
61
|
+
}
|
|
62
|
+
const inBetween = (intervals, i) => {
|
|
63
|
+
for (const interval of intervals) {
|
|
64
|
+
for (const { start, end } of interval) {
|
|
65
|
+
if (i >= start && i <= end) {
|
|
66
|
+
return { start, end };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const getNotEmptyArray = (a, b) => a.length > 0 ? a : b;
|
|
72
|
+
const findCurrentReplace = (found) => {
|
|
73
|
+
const result = new Map();
|
|
74
|
+
for (const entry of found) {
|
|
75
|
+
const existing = result.get(entry.i);
|
|
76
|
+
if (!existing || entry.searchTerm.length >= existing.searchTerm.length) {
|
|
77
|
+
result.set(entry.i, entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [...result.values()].sort((a, b) => a.i - b.i)[0];
|
|
81
|
+
};
|
|
82
|
+
// Ignore if search is in i18n key: replace "id" in "{{id.key}}" with
|
|
83
|
+
// "customer.id" and we need only the next one in string
|
|
84
|
+
found.forEach(index => index.inBetween = inBetween(ignoredStrings, index.i));
|
|
85
|
+
const foundToReplace = getNotEmptyArray(found.filter(index => !index.inBetween), found);
|
|
86
|
+
const currentReplace = findCurrentReplace(foundToReplace);
|
|
87
|
+
if (currentReplace.inBetween) {
|
|
88
|
+
start = currentReplace.inBetween.end;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
content = content.substring(0, currentReplace.i)
|
|
92
|
+
+ currentReplace.replacement
|
|
93
|
+
+ content.substring(currentReplace.i + currentReplace.searchTerm.length);
|
|
94
|
+
start = currentReplace.i + currentReplace.replacement.length;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
51
97
|
}
|
|
98
|
+
const findAllOccurrences = (string, substring, start) => {
|
|
99
|
+
if (!substring) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const indices = [];
|
|
103
|
+
let index = start;
|
|
104
|
+
while ((index = string.indexOf(substring, index)) !== -1) {
|
|
105
|
+
indices.push(index);
|
|
106
|
+
index += substring.length; // shift from current finding
|
|
107
|
+
}
|
|
108
|
+
return indices;
|
|
109
|
+
};
|
|
52
110
|
export function insertInArray(array, index, insert) {
|
|
53
111
|
array.splice(index, 0, insert);
|
|
54
112
|
}
|
|
@@ -64,9 +122,8 @@ export function writeTempAnnotations({ writeTempFiles }, name, language, content
|
|
|
64
122
|
fs.writeFileSync(path.join(TEMP_DIST_FOLDER, name + ".xml"), content);
|
|
65
123
|
}
|
|
66
124
|
}
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
return filePath.substring(0, lastIndexOf);
|
|
125
|
+
export function trimExtension(filePath) {
|
|
126
|
+
return filePath.replace(/\.[^/.]+$/, "");
|
|
70
127
|
}
|
|
71
128
|
export function traverse(json, paths, callback) {
|
|
72
129
|
if (!json) {
|
|
@@ -101,8 +158,9 @@ export function logBuilderVersion() {
|
|
|
101
158
|
const packageJson = fs.readFileSync(path.join(__dirname, "../../package.json"), { encoding: "utf-8" });
|
|
102
159
|
const packageJsonVersion = JSON.parse(packageJson).version;
|
|
103
160
|
log.info(`Running app-variant-bundler-build with version ${packageJsonVersion}`);
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
104
162
|
}
|
|
105
|
-
catch (
|
|
163
|
+
catch (e) {
|
|
106
164
|
// do nothing
|
|
107
165
|
}
|
|
108
166
|
}
|
|
@@ -118,4 +176,19 @@ export function getUniqueName(existingNames, template) {
|
|
|
118
176
|
} while (existingNames.includes(template + suffixString));
|
|
119
177
|
return template + suffixString;
|
|
120
178
|
}
|
|
179
|
+
export function getI18nPropertyKeys(files) {
|
|
180
|
+
const keys = new Set();
|
|
181
|
+
files.forEach((content, filename) => {
|
|
182
|
+
if (filename.endsWith(".properties")) {
|
|
183
|
+
const lines = content.split("\n").filter(line => !line.startsWith("#"));
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
const [key] = line.split("=");
|
|
186
|
+
if (key) {
|
|
187
|
+
keys.add(key);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return [...keys];
|
|
193
|
+
}
|
|
121
194
|
//# sourceMappingURL=commonUtil.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default class FilesUtil {
|
|
2
|
+
static filter(files: ReadonlyMap<string, string>): ReadonlyMap<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* Renames files in the base application substituting original id with
|
|
5
|
+
* appVariant id. Renames all files except i18n properties files, when we
|
|
6
|
+
* rename property keys of nested application variants we got in the end
|
|
7
|
+
* multiple i18n properties with the same key, but different values. UI5
|
|
8
|
+
* takes the first one which is the oldest but we need the latest most
|
|
9
|
+
* recent updated one.
|
|
10
|
+
* @param files - The files of the base application.
|
|
11
|
+
* @param reference - The reference of the app variant (original/base app id).
|
|
12
|
+
* @param id - The id of the app variant.
|
|
13
|
+
* @returns A map of renamed files.
|
|
14
|
+
*/
|
|
15
|
+
static rename(files: ReadonlyMap<string, string>, references: string[], adaptationProjectId: string): ReadonlyMap<string, string>;
|
|
16
|
+
private static ignore;
|
|
17
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { rename, getI18nPropertyKeys } from "./commonUtil.js";
|
|
2
|
+
import ManifestHandler from "./renamingHandlers/manifestHandler.js";
|
|
3
|
+
export default class FilesUtil {
|
|
4
|
+
static filter(files) {
|
|
5
|
+
const result = new Map();
|
|
6
|
+
files.forEach((content, filename) => {
|
|
7
|
+
if (!this.ignore(filename)) {
|
|
8
|
+
result.set(filename, content);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renames files in the base application substituting original id with
|
|
15
|
+
* appVariant id. Renames all files except i18n properties files, when we
|
|
16
|
+
* rename property keys of nested application variants we got in the end
|
|
17
|
+
* multiple i18n properties with the same key, but different values. UI5
|
|
18
|
+
* takes the first one which is the oldest but we need the latest most
|
|
19
|
+
* recent updated one.
|
|
20
|
+
* @param files - The files of the base application.
|
|
21
|
+
* @param reference - The reference of the app variant (original/base app id).
|
|
22
|
+
* @param id - The id of the app variant.
|
|
23
|
+
* @returns A map of renamed files.
|
|
24
|
+
*/
|
|
25
|
+
static rename(files, references, adaptationProjectId) {
|
|
26
|
+
const handlers = [new ManifestHandler()];
|
|
27
|
+
const IGNORE_EXTENSIONS = [".properties"];
|
|
28
|
+
const ignoreInStrings = getI18nPropertyKeys(files);
|
|
29
|
+
const renamedFiles = new Map();
|
|
30
|
+
for (const handler of handlers) {
|
|
31
|
+
handler.before(files);
|
|
32
|
+
}
|
|
33
|
+
files.forEach((content, filename) => {
|
|
34
|
+
if (!IGNORE_EXTENSIONS.some(ext => filename.endsWith(ext))) {
|
|
35
|
+
content = rename(content, references, adaptationProjectId, ignoreInStrings);
|
|
36
|
+
}
|
|
37
|
+
renamedFiles.set(filename, content);
|
|
38
|
+
});
|
|
39
|
+
for (const handler of handlers) {
|
|
40
|
+
handler.after(renamedFiles);
|
|
41
|
+
}
|
|
42
|
+
return renamedFiles;
|
|
43
|
+
}
|
|
44
|
+
static ignore(filename) {
|
|
45
|
+
const IGNORE_FILES = ["manifest.appdescr_variant"];
|
|
46
|
+
return filename.startsWith("changes/manifest/") || IGNORE_FILES.includes(filename);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=filesUtil.js.map
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
copyPathsRegex: RegExp[];
|
|
1
|
+
import AppVariant from "../appVariantManager.js";
|
|
2
|
+
import { IChange } from "../model/types.js";
|
|
3
|
+
export default class FileMerger {
|
|
4
|
+
static analyzeAppVariantManifestChanges(manifestChanges: ReadonlyArray<IChange>): {
|
|
5
|
+
mergePaths: RegExp[];
|
|
6
|
+
copyPaths: RegExp[];
|
|
8
7
|
};
|
|
9
|
-
static
|
|
8
|
+
static merge(baseAppFiles: ReadonlyMap<string, string>, i18nPath: string, appVariant: AppVariant): Map<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Filters out specific lines from the given string.
|
|
11
|
+
* Removes lines matching:
|
|
12
|
+
* - __ldi.translation.uuid\s*=\s*(.*)
|
|
13
|
+
* - ABAP_TRANSLATION
|
|
14
|
+
* - SAPUI5 TRANSLATION-KEY
|
|
15
|
+
*/
|
|
16
|
+
private static filterTranslationMetaLines;
|
|
10
17
|
/**
|
|
11
18
|
* Merge/Append base property file with property file from app variant
|
|
12
19
|
* FIXME Currently merge could duplicate keys which causes undefined
|
|
@@ -16,16 +23,4 @@ export default class I18NMerger {
|
|
|
16
23
|
* existing overwritten keys (as there should be none)
|
|
17
24
|
*/
|
|
18
25
|
private static mergePropertiesFiles;
|
|
19
|
-
/**
|
|
20
|
-
* update the path of app variant property file so it will be copied into
|
|
21
|
-
<app_variant_id> folder
|
|
22
|
-
*/
|
|
23
|
-
private static moveToAppVarSubfolder;
|
|
24
|
-
/**
|
|
25
|
-
* create new i18n file in case e.g. translation file does not exist in base
|
|
26
|
-
* app but in variant and copy of translation file is needed
|
|
27
|
-
*/
|
|
28
|
-
private static createFile;
|
|
29
|
-
private static mergeFiles;
|
|
30
26
|
}
|
|
31
|
-
export {};
|
package/dist/util/i18nMerger.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { dotToUnderscore, escapeRegex,
|
|
2
|
-
import ResourceUtil from "./resourceUtil.js";
|
|
1
|
+
import { dotToUnderscore, escapeRegex, trimExtension } from "./commonUtil.js";
|
|
3
2
|
import { posix as path } from "path";
|
|
4
|
-
export default class
|
|
5
|
-
static analyzeAppVariantManifestChanges(
|
|
3
|
+
export default class FileMerger {
|
|
4
|
+
static analyzeAppVariantManifestChanges(manifestChanges) {
|
|
6
5
|
// check which files need to be copied and which files need to be merged and copied
|
|
7
6
|
// this is necessary because lrep does not support multiple enhanceWith with multiple locations
|
|
7
|
+
const TRANSLATION_REGEX_PATTERN = "((_[a-z]{2,3})?(_[a-zA-Z]{2,3}(_[a-zA-Z]{2,20})?)?)\.properties$";
|
|
8
8
|
const mergePaths = new Set();
|
|
9
9
|
const copyPaths = new Set();
|
|
10
|
-
|
|
10
|
+
manifestChanges.forEach((change) => {
|
|
11
11
|
const i18nPathWithExtension = change.content?.bundleUrl || change.texts?.i18n;
|
|
12
12
|
if (i18nPathWithExtension) {
|
|
13
13
|
// build regex to match specific + language related files
|
|
14
|
-
const i18nPath =
|
|
15
|
-
const
|
|
16
|
-
const regex = new RegExp(escapeRegex(resourcePath) + tranlsationRegexPattern);
|
|
14
|
+
const i18nPath = trimExtension(i18nPathWithExtension);
|
|
15
|
+
const regex = new RegExp("^" + escapeRegex(i18nPath) + TRANSLATION_REGEX_PATTERN);
|
|
17
16
|
if (change.changeType.includes("addNewModelEnhanceWith")) {
|
|
18
17
|
copyPaths.add(regex);
|
|
19
18
|
}
|
|
@@ -22,36 +21,44 @@ export default class I18NMerger {
|
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
});
|
|
25
|
-
return {
|
|
24
|
+
return { mergePaths: Array.from(mergePaths), copyPaths: Array.from(copyPaths) };
|
|
26
25
|
}
|
|
27
|
-
static
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
for (const appVariantResource of appVariantResources) {
|
|
34
|
-
const appVariantResourcePath = appVariantResource.getPath();
|
|
35
|
-
if (appVariantResourcePath.endsWith(".properties")) {
|
|
26
|
+
static merge(baseAppFiles, i18nPath, appVariant) {
|
|
27
|
+
const i18nTargetFolder = dotToUnderscore(appVariant.id);
|
|
28
|
+
const { copyPaths, mergePaths } = this.analyzeAppVariantManifestChanges(appVariant.getProcessedManifestChanges());
|
|
29
|
+
const files = new Map(baseAppFiles);
|
|
30
|
+
for (const [filename, content] of Array.from(appVariant.files)) {
|
|
31
|
+
if (filename.endsWith(".properties")) {
|
|
36
32
|
// merge/copy logic
|
|
37
33
|
// check if file matches with regex in merge/copy
|
|
38
|
-
const mergePathMatch =
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let baseAppI18NPath = `${rootFolder}/${baseAppManifestI18NPath}${mergePathMatch[1] || ""}.properties`;
|
|
43
|
-
await this.mergePropertiesFiles(aggregatedResourceFilesMap, appVariantResource, baseAppI18NPath);
|
|
34
|
+
const mergePathMatch = mergePaths.map(path => filename.match(path)).find(match => match);
|
|
35
|
+
const copyPathMatch = copyPaths.map(path => filename.match(path)).find(match => match);
|
|
36
|
+
if (mergePathMatch) {
|
|
37
|
+
this.mergePropertiesFiles(files, i18nPath, content, mergePathMatch[1]);
|
|
44
38
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.moveToAppVarSubfolder(appVariantResource, rootFolder, i18nTargetFolder);
|
|
48
|
-
if (!shouldCopyFile) {
|
|
49
|
-
taskUtil.setTag(appVariantResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult, true);
|
|
39
|
+
if (copyPathMatch) {
|
|
40
|
+
files.set(path.join(i18nTargetFolder, filename), content);
|
|
50
41
|
}
|
|
51
42
|
}
|
|
52
|
-
|
|
43
|
+
else {
|
|
44
|
+
files.set(filename, content);
|
|
45
|
+
}
|
|
53
46
|
}
|
|
54
|
-
return
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Filters out specific lines from the given string.
|
|
51
|
+
* Removes lines matching:
|
|
52
|
+
* - __ldi.translation.uuid\s*=\s*(.*)
|
|
53
|
+
* - ABAP_TRANSLATION
|
|
54
|
+
* - SAPUI5 TRANSLATION-KEY
|
|
55
|
+
*/
|
|
56
|
+
static filterTranslationMetaLines(content) {
|
|
57
|
+
const lines = content.split('\n');
|
|
58
|
+
const filtered = lines.filter(line => !/^# __ldi\.translation\.uuid\s*=/.test(line) &&
|
|
59
|
+
!line.startsWith("# ABAP_TRANSLATION") &&
|
|
60
|
+
!line.startsWith("# SAPUI5 TRANSLATION-KEY"));
|
|
61
|
+
return filtered.join('\n');
|
|
55
62
|
}
|
|
56
63
|
/**
|
|
57
64
|
* Merge/Append base property file with property file from app variant
|
|
@@ -61,39 +68,14 @@ export default class I18NMerger {
|
|
|
61
68
|
* app variant Id as prefix => If we filter on them we do not need to remove
|
|
62
69
|
* existing overwritten keys (as there should be none)
|
|
63
70
|
*/
|
|
64
|
-
static
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await this.createFile(aggregatedResourceFilesMap, baseAppI18NPath, variantResource);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* update the path of app variant property file so it will be copied into
|
|
77
|
-
<app_variant_id> folder
|
|
78
|
-
*/
|
|
79
|
-
static moveToAppVarSubfolder(variantResource, rootFolder, i18nBundleName) {
|
|
80
|
-
const relativeFilePath = variantResource.getPath().substring(rootFolder.length);
|
|
81
|
-
const newResourcePath = path.join(rootFolder, i18nBundleName, relativeFilePath);
|
|
82
|
-
variantResource.setPath(newResourcePath);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* create new i18n file in case e.g. translation file does not exist in base
|
|
86
|
-
* app but in variant and copy of translation file is needed
|
|
87
|
-
*/
|
|
88
|
-
static async createFile(aggregatedResourceFilesMap, path, resource) {
|
|
89
|
-
const createdFile = await resource.clone();
|
|
90
|
-
createdFile.setPath(path);
|
|
91
|
-
aggregatedResourceFilesMap.set(path, createdFile);
|
|
92
|
-
}
|
|
93
|
-
static async mergeFiles(baseFile, variantFile) {
|
|
94
|
-
const variantFileContent = await variantFile.getString();
|
|
95
|
-
const mergedFileContent = await baseFile.getString();
|
|
96
|
-
baseFile.setString(`${mergedFileContent}\n\n#App variant specific text file\n\n${variantFileContent}`);
|
|
71
|
+
static mergePropertiesFiles(files, i18nPath, appVariantFileContent, language = "") {
|
|
72
|
+
const baseAppI18nPath = i18nPath + language + ".properties";
|
|
73
|
+
const baseAppFileContent = files.get(baseAppI18nPath);
|
|
74
|
+
const filteredBaseContent = baseAppFileContent ? this.filterTranslationMetaLines(baseAppFileContent) : "";
|
|
75
|
+
const content = filteredBaseContent
|
|
76
|
+
? `${filteredBaseContent}\n\n#App variant specific text file\n\n${appVariantFileContent}`
|
|
77
|
+
: appVariantFileContent;
|
|
78
|
+
files.set(baseAppI18nPath, content);
|
|
97
79
|
}
|
|
98
80
|
}
|
|
99
81
|
//# sourceMappingURL=i18nMerger.js.map
|