@ui5/task-adaptation 1.4.2 → 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 +16 -1
- package/README.md +16 -12
- package/REUSE.toml +10 -0
- package/dist/annotationManager.d.ts +1 -1
- package/dist/annotationManager.js +5 -8
- package/dist/annotations/dataSource/dataSource.js +0 -1
- 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 +58 -81
- package/dist/bundle.d.ts +2 -3
- package/dist/bundle.js +3834 -3490
- 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/configuration.d.ts +2 -2
- package/dist/model/language.d.ts +3 -3
- package/dist/model/language.js +11 -4
- package/dist/model/types.d.ts +3 -1
- 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 +29 -13
- package/dist/processors/processor.d.ts +4 -2
- package/dist/processors/processor.js +3 -5
- package/dist/repositories/abapRepoManager.d.ts +3 -1
- package/dist/repositories/abapRepoManager.js +24 -5
- package/dist/repositories/html5RepoManager.js +3 -2
- package/dist/util/cfUtil.d.ts +1 -0
- package/dist/util/cfUtil.js +27 -23
- package/dist/util/commonUtil.d.ts +5 -3
- package/dist/util/commonUtil.js +104 -31
- 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/requestUtil.d.ts +2 -1
- package/dist/util/resourceUtil.d.ts +3 -1
- package/dist/util/resourceUtil.js +15 -2
- package/eslint.config.js +20 -4
- package/package.json +28 -22
- package/scripts/rollup/bundle.d.ts +2 -3
- package/scripts/rollup/bundleDefinition.js +2 -2
- package/scripts/rollup/overrides/sap/ui/fl/Utils.js +13 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/changes/FlexCustomData.js +13 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/flexObjects/AnnotationChange.js +11 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/flexObjects/AppDescriptorChange.js +68 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/flexObjects/VariantChange.js +11 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/flexObjects/VariantManagementChange.js +11 -0
- package/scripts/rollup/overrides/sap/ui/fl/apply/_internal/flexState/controlVariants/VariantManagementState.js +13 -0
- package/scripts/rollup/overrides/sap/ui/fl/initial/_internal/changeHandlers/ChangeHandlerRegistration.js +13 -0
- package/scripts/rollup/overrides/sap/ui/fl/initial/_internal/changeHandlers/ChangeHandlerStorage.js +14 -0
- package/scripts/rollup/project/ui5.yaml +1 -1
- 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
- package/scripts/rollup/overrides/sap/ui/fl/Change.js +0 -74
package/dist/util/cfUtil.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { cfCreateService, cfGetInstanceCredentials
|
|
1
|
+
import { cfCreateService, cfGetInstanceCredentials } from "@sap/cf-tools/out/src/cf-local.js";
|
|
2
|
+
import { getSpaceGuidThrowIfUndefined } from "@sap/cf-tools/out/src/utils.js";
|
|
2
3
|
import { Cli } from "@sap/cf-tools/out/src/cli.js";
|
|
3
4
|
import { eFilters } from "@sap/cf-tools/out/src/types.js";
|
|
4
5
|
import { getLogger } from "@ui5/logger";
|
|
@@ -34,16 +35,20 @@ export default class CFUtil {
|
|
|
34
35
|
}
|
|
35
36
|
static async createService(params) {
|
|
36
37
|
log.verbose(`Creating a service instance with parameters: ${JSON.stringify(params)}`);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const serviceOfferings = await this.requestCfApi(`/v3/service_offerings?names=${params.serviceName}`);
|
|
39
|
+
if (serviceOfferings.length === 0) {
|
|
40
|
+
throw new Error(`Cannot find a service offering by name '${params.serviceName}'`);
|
|
41
|
+
}
|
|
42
|
+
const plans = await this.requestCfApi(`/v3/service_plans?service_offering_guids=${serviceOfferings[0].guid}`);
|
|
43
|
+
const plan = plans.find(plan => plan.name === params.planName);
|
|
44
|
+
if (!plan) {
|
|
45
|
+
throw new Error(`Cannot find a plan by name '${params.planName}' for service '${params.serviceName}'`);
|
|
41
46
|
}
|
|
42
47
|
try {
|
|
43
|
-
await cfCreateService(
|
|
48
|
+
await cfCreateService(plan.guid, params.serviceInstanceName, params.parameters, params.tags);
|
|
44
49
|
}
|
|
45
50
|
catch (error) {
|
|
46
|
-
throw new Error(`Cannot create a service instance '${params.
|
|
51
|
+
throw new Error(`Cannot create a service instance '${params.serviceInstanceName}' in space '${params.spaceGuid}': ${error.message}`);
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
static async getOrCreateServiceKeys(serviceInstance) {
|
|
@@ -73,7 +78,7 @@ export default class CFUtil {
|
|
|
73
78
|
return this.cfExecute(["create-service-key", serviceInstanceName, serviceKeyName]);
|
|
74
79
|
}
|
|
75
80
|
catch (error) {
|
|
76
|
-
throw new Error(`Couldn't create a service key for instance: ${serviceInstanceName}`);
|
|
81
|
+
throw new Error(`Couldn't create a service key for instance: ${serviceInstanceName}: ${error}`);
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
static async getServiceInstance(params) {
|
|
@@ -92,9 +97,19 @@ export default class CFUtil {
|
|
|
92
97
|
guid: service.guid
|
|
93
98
|
}));
|
|
94
99
|
}
|
|
100
|
+
static processErrors(json) {
|
|
101
|
+
if (json?.errors?.length > 0) {
|
|
102
|
+
const message = JSON.stringify(json.errors);
|
|
103
|
+
if (json?.errors?.some((e) => e.title === "CF-NotAuthenticated" || e.code === 10002)) {
|
|
104
|
+
throw new Error(`Authentication error. Use 'cf login' to authenticate in Cloud Foundry: ${message}`);
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`Failed sending request to Cloud Foundry: ${message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
95
109
|
static async requestCfApi(url) {
|
|
96
110
|
const response = await this.cfExecute(["curl", url]);
|
|
97
111
|
const json = this.parseJson(response);
|
|
112
|
+
this.processErrors(json);
|
|
98
113
|
const resources = json?.resources;
|
|
99
114
|
const totalPages = json?.pagination?.total_pages;
|
|
100
115
|
if (totalPages > 1) {
|
|
@@ -105,7 +120,7 @@ export default class CFUtil {
|
|
|
105
120
|
return this.parseJson(response)?.resources || [];
|
|
106
121
|
})).then(resources => [].concat(...resources)));
|
|
107
122
|
}
|
|
108
|
-
return resources;
|
|
123
|
+
return resources ?? [];
|
|
109
124
|
}
|
|
110
125
|
static getOAuthToken() {
|
|
111
126
|
return this.cfExecute(["oauth-token"]);
|
|
@@ -152,20 +167,9 @@ export default class CFUtil {
|
|
|
152
167
|
* @memberof CFUtil
|
|
153
168
|
*/
|
|
154
169
|
static async getSpaceGuid(spaceGuid) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const resources = await this.requestCfApi(`/v3/spaces?names=${spaceName}`);
|
|
159
|
-
for (const resource of resources) {
|
|
160
|
-
spaceGuid = resource.guid;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (spaceGuid == null) {
|
|
166
|
-
throw new Error("Please login to Cloud Foundry with 'cf login' and try again");
|
|
167
|
-
}
|
|
168
|
-
return spaceGuid;
|
|
170
|
+
return spaceGuid ?? getSpaceGuidThrowIfUndefined().catch((e) => {
|
|
171
|
+
throw new Error("Please specify space and org guids in ui5.yaml or login to Cloud Foundry with 'cf login' and try again: " + e.message);
|
|
172
|
+
});
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
//# sourceMappingURL=cfUtil.js.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { IConfiguration } from "../model/types.js";
|
|
2
2
|
import Language from "../model/language.js";
|
|
3
3
|
export declare function dotToUnderscore(value: string): string;
|
|
4
|
-
export declare function validateObject<T extends
|
|
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,6 +158,7 @@ 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
163
|
catch (e) {
|
|
106
164
|
// do nothing
|
|
@@ -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
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class ManifestHandler {
|
|
2
|
+
appVariantIdHierarchy = [];
|
|
3
|
+
before(files) {
|
|
4
|
+
const manifest = files.get("manifest.json");
|
|
5
|
+
if (manifest) {
|
|
6
|
+
const manifestJson = JSON.parse(manifest);
|
|
7
|
+
this.appVariantIdHierarchy = manifestJson["sap.ui5"].appVariantIdHierarchy;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
after(files) {
|
|
11
|
+
const manifest = files.get("manifest.json");
|
|
12
|
+
if (manifest) {
|
|
13
|
+
const manifestJson = JSON.parse(manifest);
|
|
14
|
+
manifestJson["sap.ui5"].appVariantIdHierarchy = this.appVariantIdHierarchy;
|
|
15
|
+
files.set("manifest.json", JSON.stringify(manifestJson));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=manifestHandler.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { AxiosRequestConfig } from "axios";
|
|
1
2
|
export default class RequestUtil {
|
|
2
3
|
static head(url: string): Promise<any>;
|
|
3
4
|
static get(url: string, options?: any): Promise<any>;
|
|
4
|
-
static request(url: string, method:
|
|
5
|
+
static request(url: string, method: (url: string, config?: AxiosRequestConfig) => any, options?: any): Promise<any>;
|
|
5
6
|
private static handleError;
|
|
6
7
|
}
|
|
@@ -3,9 +3,11 @@ 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
|
-
static
|
|
6
|
+
private static _read;
|
|
7
|
+
static read(folder: string): Map<string, string>;
|
|
7
8
|
static getString(resource: any): Promise<string>;
|
|
8
9
|
static getJson(resource: any): Promise<any>;
|
|
9
10
|
static setString(resource: any, str: string): void;
|
|
10
11
|
static createResource(filename: string, projectNamespace: string, content: string): any;
|
|
12
|
+
static toFileMap(resources: ReadonlyArray<Resource>, projectNamespace: string): Promise<Map<string, string>>;
|
|
11
13
|
}
|
|
@@ -29,7 +29,7 @@ export default class ResourceUtil {
|
|
|
29
29
|
});
|
|
30
30
|
return Promise.all(promises);
|
|
31
31
|
}
|
|
32
|
-
static
|
|
32
|
+
static _read(rootFolder, folder, files, exclude = []) {
|
|
33
33
|
const entries = fs.readdirSync(folder);
|
|
34
34
|
for (let entry of entries) {
|
|
35
35
|
const entryPath = path.join(folder, entry);
|
|
@@ -39,10 +39,15 @@ export default class ResourceUtil {
|
|
|
39
39
|
files.set(normalized, fs.readFileSync(entryPath, { encoding: "utf-8" }));
|
|
40
40
|
}
|
|
41
41
|
else if (stats.isDirectory()) {
|
|
42
|
-
this.
|
|
42
|
+
this._read(rootFolder, entryPath, files, exclude);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
static read(folder) {
|
|
47
|
+
const files = new Map();
|
|
48
|
+
this._read(folder, folder, files);
|
|
49
|
+
return files;
|
|
50
|
+
}
|
|
46
51
|
static getString(resource) {
|
|
47
52
|
return resource.getBuffer().then((buffer) => buffer.toString(UTF8));
|
|
48
53
|
}
|
|
@@ -58,5 +63,13 @@ export default class ResourceUtil {
|
|
|
58
63
|
string: content
|
|
59
64
|
});
|
|
60
65
|
}
|
|
66
|
+
static async toFileMap(resources, projectNamespace) {
|
|
67
|
+
const files = new Map();
|
|
68
|
+
const rootFolderLength = ResourceUtil.getRootFolder(projectNamespace).length;
|
|
69
|
+
for (const resource of resources) {
|
|
70
|
+
files.set(resource.getPath().substring(rootFolderLength + 1), await ResourceUtil.getString(resource));
|
|
71
|
+
}
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
61
74
|
}
|
|
62
75
|
//# sourceMappingURL=resourceUtil.js.map
|