@ui5/task-adaptation 1.5.1 → 1.5.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -1
- package/dist/annotationManager.d.ts +1 -2
- package/dist/annotationManager.js +8 -13
- package/dist/appVariantManager.d.ts +15 -2
- package/dist/appVariantManager.js +46 -27
- package/dist/baseAppManager.d.ts +9 -1
- package/dist/baseAppManager.js +57 -18
- package/dist/bundle.js +97 -59
- package/dist/index.js +33 -2
- package/dist/model/configuration.d.ts +1 -0
- package/dist/processors/abapProcessor.d.ts +1 -1
- package/dist/processors/abapProcessor.js +3 -2
- package/dist/processors/cfProcessor.d.ts +3 -1
- package/dist/processors/cfProcessor.js +58 -9
- package/dist/processors/processor.d.ts +1 -1
- package/dist/util/cfUtil.d.ts +47 -0
- package/dist/util/cfUtil.js +121 -0
- package/dist/util/commonUtil.d.ts +1 -2
- package/dist/util/commonUtil.js +9 -40
- package/dist/util/filesUtil.d.ts +16 -0
- package/dist/util/filesUtil.js +83 -0
- package/dist/util/i18nMerger.js +2 -2
- package/dist/util/movingHandler/fileMoveHandler.d.ts +8 -0
- package/dist/util/movingHandler/fileMoveHandler.js +77 -0
- package/dist/util/renamingHandlers/manifestRenamingHandler.d.ts +6 -0
- package/dist/util/renamingHandlers/manifestRenamingHandler.js +20 -0
- package/dist/util/renamingHandlers/renamingHandler.d.ts +4 -0
- package/dist/util/renamingHandlers/renamingHandler.js +2 -0
- package/dist/util/renamingUtil.d.ts +3 -0
- package/dist/util/renamingUtil.js +111 -0
- package/package.json +124 -125
- package/scripts/rollup/overrides/sap/base/config.js +52 -14
- package/scripts/rollup.ts +1 -2
- package/scripts/test-integration-prep.sh +4 -0
package/dist/util/cfUtil.js
CHANGED
|
@@ -81,6 +81,12 @@ export default class CFUtil {
|
|
|
81
81
|
throw new Error(`Couldn't create a service key for instance: ${serviceInstanceName}: ${error}`);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
static deleteServiceKeyUnsafe(serviceInstanceName, serviceKeyName) {
|
|
85
|
+
// Fire and forget - async delete without waiting for result or handling errors
|
|
86
|
+
this.cfExecute(["delete-service-key", serviceInstanceName, serviceKeyName, "-f"]).catch(() => {
|
|
87
|
+
// Ignore any errors - this is intentionally unsafe
|
|
88
|
+
});
|
|
89
|
+
}
|
|
84
90
|
static async getServiceInstance(params) {
|
|
85
91
|
const PARAM_MAP = {
|
|
86
92
|
spaceGuids: "space_guids",
|
|
@@ -159,6 +165,121 @@ export default class CFUtil {
|
|
|
159
165
|
throw new Error(`Failed parse response from request CF API: ${error.message}`);
|
|
160
166
|
}
|
|
161
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Get service keys and return the first one with valid endpoints
|
|
170
|
+
* @private
|
|
171
|
+
* @static
|
|
172
|
+
* @param {string} serviceInstanceGuid the service instance guid
|
|
173
|
+
* @return {Promise<any>} the first service key with valid endpoints, or null if none found
|
|
174
|
+
* @memberof CFUtil
|
|
175
|
+
*/
|
|
176
|
+
static async getServiceKeyWithValidEndpoints(serviceInstanceGuid) {
|
|
177
|
+
try {
|
|
178
|
+
const serviceKeys = await this.getServiceKeys(serviceInstanceGuid);
|
|
179
|
+
// Find and return the first key with valid endpoints
|
|
180
|
+
return serviceKeys.find((key) => key.credentials?.endpoints && this.hasValidEndpoints(key.credentials.endpoints));
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
throw new Error("Failed to get service credentials: " + error.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get service keys for a service instance by name. If the existing service key
|
|
188
|
+
* has endpoints as strings instead of objects, a new service key will be created.
|
|
189
|
+
* @static
|
|
190
|
+
* @param {string} serviceInstanceName name of the service instance
|
|
191
|
+
* @param {string} [spaceGuid] optional space guid, will use current space if not provided
|
|
192
|
+
* @return {Promise<any>} promise with service key credentials
|
|
193
|
+
* @memberof CFUtil
|
|
194
|
+
*/
|
|
195
|
+
static async getOrCreateServiceKeyWithEndpoints(serviceInstanceName, spaceGuid) {
|
|
196
|
+
const resolvedSpaceGuid = await this.getSpaceGuid(spaceGuid);
|
|
197
|
+
// Find service instance by name
|
|
198
|
+
const serviceInstances = await this.getServiceInstance({
|
|
199
|
+
names: [serviceInstanceName],
|
|
200
|
+
spaceGuids: [resolvedSpaceGuid]
|
|
201
|
+
});
|
|
202
|
+
if (!(serviceInstances?.length > 0)) {
|
|
203
|
+
throw new Error(`Cannot find service instance '${serviceInstanceName}' in space: ${resolvedSpaceGuid}`);
|
|
204
|
+
}
|
|
205
|
+
const serviceInstance = serviceInstances[0];
|
|
206
|
+
log.verbose(`Found service instance '${serviceInstance.name}' with guid: ${serviceInstance.guid}`);
|
|
207
|
+
// If no valid service key found, create a new one with unique name
|
|
208
|
+
const uniqueServiceKeyNamePromise = this.generateUniqueServiceKeyName(serviceInstance.name, serviceInstance.guid);
|
|
209
|
+
// Get service keys with credentials to find any with valid endpoints:
|
|
210
|
+
// object with url and destination instead of a single url string
|
|
211
|
+
const validKey = await this.getServiceKeyWithValidEndpoints(serviceInstance.guid);
|
|
212
|
+
if (validKey) {
|
|
213
|
+
log.verbose(`Using existing service key with valid endpoints structure`);
|
|
214
|
+
return validKey.credentials;
|
|
215
|
+
}
|
|
216
|
+
const uniqueServiceKeyName = await uniqueServiceKeyNamePromise;
|
|
217
|
+
log.info(`No valid service key found with proper endpoints structure. Creating new service key '${uniqueServiceKeyName}' for '${serviceInstance.name}'`);
|
|
218
|
+
await this.createServiceKey(serviceInstance.name, uniqueServiceKeyName);
|
|
219
|
+
// Get the newly created service key and validate its endpoints
|
|
220
|
+
const newValidKey = await this.getServiceKeyWithValidEndpoints(serviceInstance.guid);
|
|
221
|
+
if (newValidKey) {
|
|
222
|
+
log.verbose(`Using newly created service key with valid endpoints structure`);
|
|
223
|
+
return newValidKey.credentials;
|
|
224
|
+
}
|
|
225
|
+
// Clean up the created service key since it doesn't have valid
|
|
226
|
+
// endpoints. We don't throw an error here even if no valid endpoints
|
|
227
|
+
// found. We assume that there might be no coincidence between
|
|
228
|
+
// credential endpoints and xs-app.json destinations.
|
|
229
|
+
this.deleteServiceKeyUnsafe(serviceInstance.name, uniqueServiceKeyName);
|
|
230
|
+
log.verbose(`Created service key '${uniqueServiceKeyName}' does not have valid endpoints structure. Triggered deletion of invalid service key '${uniqueServiceKeyName}'`);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Check if endpoints object has at least one property that is an object
|
|
234
|
+
* @private
|
|
235
|
+
* @static
|
|
236
|
+
* @param {any} endpoints the endpoints object to validate
|
|
237
|
+
* @return {boolean} true if at least one property of endpoints is an object
|
|
238
|
+
* @memberof CFUtil
|
|
239
|
+
*/
|
|
240
|
+
static hasValidEndpoints(endpoints) {
|
|
241
|
+
if (!endpoints || typeof endpoints !== 'object' || Array.isArray(endpoints)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
// Check if at least one property of endpoints is an object
|
|
245
|
+
return Object.values(endpoints).some(value => value && typeof value === 'object' && !Array.isArray(value));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get all service key names for a service instance
|
|
249
|
+
* @private
|
|
250
|
+
* @static
|
|
251
|
+
* @param {string} serviceInstanceGuid the service instance guid
|
|
252
|
+
* @return {Promise<string[]>} promise with array of service key names
|
|
253
|
+
* @memberof CFUtil
|
|
254
|
+
*/
|
|
255
|
+
static async getAllServiceKeyNames(serviceInstanceGuid) {
|
|
256
|
+
try {
|
|
257
|
+
const serviceKeys = await this.requestCfApi(`/v3/service_credential_bindings?type=key&service_instance_guids=${serviceInstanceGuid}`);
|
|
258
|
+
return serviceKeys.map((key) => key.name);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
throw new Error(`Failed to get service key names: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Generate a unique service key name in format serviceInstanceName-key-N
|
|
266
|
+
* @static
|
|
267
|
+
* @param {string} serviceInstanceName the service instance name
|
|
268
|
+
* @param {string} serviceInstanceGuid the service instance guid
|
|
269
|
+
* @return {Promise<string>} promise with unique service key name
|
|
270
|
+
* @memberof CFUtil
|
|
271
|
+
*/
|
|
272
|
+
static async generateUniqueServiceKeyName(serviceInstanceName, serviceInstanceGuid) {
|
|
273
|
+
const existingKeyNames = await this.getAllServiceKeyNames(serviceInstanceGuid);
|
|
274
|
+
let counter = 0;
|
|
275
|
+
let keyName;
|
|
276
|
+
do {
|
|
277
|
+
keyName = `${serviceInstanceName}-key-${counter}`;
|
|
278
|
+
counter++;
|
|
279
|
+
} while (existingKeyNames.includes(keyName));
|
|
280
|
+
log.verbose(`Generated unique service key name: ${keyName}`);
|
|
281
|
+
return keyName;
|
|
282
|
+
}
|
|
162
283
|
/**
|
|
163
284
|
* Get space guid from configuration or local CF fodler
|
|
164
285
|
* @static
|
|
@@ -3,8 +3,6 @@ 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: ReadonlyMap<string, string>, search: string, replacement: string): Map<string, string>;
|
|
7
|
-
export declare function rename(content: string, search: string, replacement: string): string;
|
|
8
6
|
export declare function insertInArray<T>(array: T[], index: number, insert: T): void;
|
|
9
7
|
export declare function writeTempAnnotations({ writeTempFiles }: IConfiguration, name: string, language: Language, content: string): void;
|
|
10
8
|
export declare function trimExtension(filePath: string): string;
|
|
@@ -12,3 +10,4 @@ export declare function traverse(json: any, paths: string[], callback: (json: an
|
|
|
12
10
|
export declare function logBuilderVersion(): void;
|
|
13
11
|
export declare function logBetaUsage(): void;
|
|
14
12
|
export declare function getUniqueName(existingNames: string[], template: string): string;
|
|
13
|
+
export declare function isManifestChange(filename: string, content: string): boolean;
|
package/dist/util/commonUtil.js
CHANGED
|
@@ -4,6 +4,8 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
import { posix as path } from "path";
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const log = Log.getLogger("rollup-plugin-ui5-resolve-task-adaptation");
|
|
7
|
+
const CHANGES_EXT = ".change";
|
|
8
|
+
const MANIFEST_CHANGE = "appdescr_";
|
|
7
9
|
export function dotToUnderscore(value) {
|
|
8
10
|
return value.replace(/\./g, "_");
|
|
9
11
|
}
|
|
@@ -17,46 +19,6 @@ export function validateObject(options, properties, message) {
|
|
|
17
19
|
export function escapeRegex(update) {
|
|
18
20
|
return update.replaceAll(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
|
19
21
|
}
|
|
20
|
-
export function renameResources(files, search, replacement) {
|
|
21
|
-
const replaces = getReplaceRegex(search, replacement);
|
|
22
|
-
const renamedFiles = new Map();
|
|
23
|
-
files.forEach((content, filepath) => {
|
|
24
|
-
renamedFiles.set(filepath, replaces.reduce((p, c) => p.replace(c.regexp, c.replacement), content));
|
|
25
|
-
});
|
|
26
|
-
return renamedFiles;
|
|
27
|
-
}
|
|
28
|
-
function getReplaceRegex(search, replacement) {
|
|
29
|
-
// The current regex works if the old Id is contained in the new Id, given
|
|
30
|
-
// that they do not have the same beginning.
|
|
31
|
-
// more complete alternative: /((?<!newIdStart)|(?!newIdEnd))oldId/g
|
|
32
|
-
let escapedSearch;
|
|
33
|
-
if (replacement.includes(search)) {
|
|
34
|
-
const [before] = replacement.split(search);
|
|
35
|
-
// Matches a position in the string that is not immediately preceded by
|
|
36
|
-
// the string "before". Since we won't replace anyway, we should also
|
|
37
|
-
// ignore one with the slashes.
|
|
38
|
-
const escapedBefore = escapeRegex(before).replaceAll("\\.", "[\\./]");
|
|
39
|
-
escapedSearch = `(?<!${escapedBefore})${escapeRegex(search)}`;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
escapedSearch = escapeRegex(search);
|
|
43
|
-
}
|
|
44
|
-
const dotToSlash = (update) => update.replaceAll(".", "\/");
|
|
45
|
-
return [
|
|
46
|
-
{
|
|
47
|
-
regexp: new RegExp(escapedSearch, "g"),
|
|
48
|
-
replacement
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
regexp: new RegExp(dotToSlash(escapedSearch), "g"),
|
|
52
|
-
replacement: dotToSlash(replacement)
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
}
|
|
56
|
-
export function rename(content, search, replacement) {
|
|
57
|
-
const replaces = getReplaceRegex(search, replacement);
|
|
58
|
-
return replaces.reduce((p, c) => p.replace(c.regexp, c.replacement), content);
|
|
59
|
-
}
|
|
60
22
|
export function insertInArray(array, index, insert) {
|
|
61
23
|
array.splice(index, 0, insert);
|
|
62
24
|
}
|
|
@@ -126,4 +88,11 @@ export function getUniqueName(existingNames, template) {
|
|
|
126
88
|
} while (existingNames.includes(template + suffixString));
|
|
127
89
|
return template + suffixString;
|
|
128
90
|
}
|
|
91
|
+
export function isManifestChange(filename, content) {
|
|
92
|
+
if (filename.endsWith(CHANGES_EXT)) {
|
|
93
|
+
const change = JSON.parse(content);
|
|
94
|
+
return change.changeType?.startsWith(MANIFEST_CHANGE);
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
129
98
|
//# sourceMappingURL=commonUtil.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default class FilesUtil {
|
|
2
|
+
static filter(files: ReadonlyMap<string, string>): ReadonlyMap<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* 1p. i18n.properties are not renamed and property keys are ignored during
|
|
5
|
+
* the renaming across the files.
|
|
6
|
+
* @param files - all files both base and appVariants
|
|
7
|
+
* @param references - Reference maps of the app variant
|
|
8
|
+
* @returns A map of renamed files
|
|
9
|
+
*/
|
|
10
|
+
static rename(files: ReadonlyMap<string, string>, references: Map<string, string>): ReadonlyMap<string, string>;
|
|
11
|
+
private static getI18nPropertyKeys;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* We might rename appVariantIdHierarchy, so we restore it after the renaming.
|
|
15
|
+
*/
|
|
16
|
+
export declare function restoreThatShouldntBeRenamed(): (_target: any, _propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
};
|
|
7
|
+
import { renameMap } from "./renamingUtil.js";
|
|
8
|
+
import ManifestRenamingHandler from "./renamingHandlers/manifestRenamingHandler.js";
|
|
9
|
+
export default class FilesUtil {
|
|
10
|
+
static filter(files) {
|
|
11
|
+
const shouldIgnore = (filename, content) => {
|
|
12
|
+
const IGNORE_FILES = ["manifest.appdescr_variant"];
|
|
13
|
+
if (filename.endsWith(".change")) {
|
|
14
|
+
return JSON.parse(content).changeType?.startsWith("appdescr_"); // validate JSON
|
|
15
|
+
}
|
|
16
|
+
return IGNORE_FILES.includes(filename);
|
|
17
|
+
};
|
|
18
|
+
const result = new Map();
|
|
19
|
+
files.forEach((content, filename) => {
|
|
20
|
+
if (!shouldIgnore(filename, content)) {
|
|
21
|
+
result.set(filename, content);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 1p. i18n.properties are not renamed and property keys are ignored during
|
|
28
|
+
* the renaming across the files.
|
|
29
|
+
* @param files - all files both base and appVariants
|
|
30
|
+
* @param references - Reference maps of the app variant
|
|
31
|
+
* @returns A map of renamed files
|
|
32
|
+
*/
|
|
33
|
+
static rename(files, references) {
|
|
34
|
+
const IGNORE_EXTENSIONS = [".properties"];
|
|
35
|
+
const ignoreInString = this.getI18nPropertyKeys(files);
|
|
36
|
+
return new Map(Array.from(files, ([filename, content]) => {
|
|
37
|
+
if (!IGNORE_EXTENSIONS.some(ext => filename.endsWith(ext))) {
|
|
38
|
+
// 5p. We pass replacements as ignores since we don't want to
|
|
39
|
+
// rename them again. E.g. we replace app.id with
|
|
40
|
+
// customer.app.id, but if we found customer.app.id somewhere,
|
|
41
|
+
// because we applied changes or something, we don't want to
|
|
42
|
+
// rename it again to customer.customer.app.id.
|
|
43
|
+
content = renameMap(content, references, [...references.values(), ...ignoreInString]);
|
|
44
|
+
}
|
|
45
|
+
return [filename, content];
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
static getI18nPropertyKeys(files) {
|
|
49
|
+
const keys = new Set();
|
|
50
|
+
files.forEach((content, filename) => {
|
|
51
|
+
if (filename.endsWith(".properties")) {
|
|
52
|
+
const lines = content.split("\n").filter(line => !line.startsWith("#"));
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const [key] = line.split("=");
|
|
55
|
+
if (key) {
|
|
56
|
+
keys.add(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return [...keys];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
__decorate([
|
|
65
|
+
restoreThatShouldntBeRenamed()
|
|
66
|
+
], FilesUtil, "rename", null);
|
|
67
|
+
/**
|
|
68
|
+
* We might rename appVariantIdHierarchy, so we restore it after the renaming.
|
|
69
|
+
*/
|
|
70
|
+
export function restoreThatShouldntBeRenamed() {
|
|
71
|
+
return function (_target, _propertyKey, descriptor) {
|
|
72
|
+
const handlers = [new ManifestRenamingHandler()];
|
|
73
|
+
const originalValue = descriptor.value;
|
|
74
|
+
descriptor.value = function (...args) {
|
|
75
|
+
handlers.forEach(handler => handler.before(args[0]));
|
|
76
|
+
const renamedFiles = originalValue.apply(this, args);
|
|
77
|
+
handlers.forEach(handler => handler.after(renamedFiles));
|
|
78
|
+
return renamedFiles;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
;
|
|
83
|
+
//# sourceMappingURL=filesUtil.js.map
|
package/dist/util/i18nMerger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escapeRegex, trimExtension } from "./commonUtil.js";
|
|
2
2
|
import { posix as path } from "path";
|
|
3
3
|
export default class FileMerger {
|
|
4
4
|
static analyzeAppVariantManifestChanges(manifestChanges) {
|
|
@@ -24,7 +24,7 @@ export default class FileMerger {
|
|
|
24
24
|
return { mergePaths: Array.from(mergePaths), copyPaths: Array.from(copyPaths) };
|
|
25
25
|
}
|
|
26
26
|
static merge(baseAppFiles, i18nPath, appVariant) {
|
|
27
|
-
const i18nTargetFolder =
|
|
27
|
+
const i18nTargetFolder = appVariant.prefix;
|
|
28
28
|
const { copyPaths, mergePaths } = this.analyzeAppVariantManifestChanges(appVariant.getProcessedManifestChanges());
|
|
29
29
|
const files = new Map(baseAppFiles);
|
|
30
30
|
for (const [filename, content] of Array.from(appVariant.getProcessedFiles())) {
|
|
@@ -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=fileMoveHandler.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class ManifestRenamingHandler {
|
|
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=manifestRenamingHandler.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function rename(content: string, references: string[], replacement: string): string;
|
|
2
|
+
export declare function renameMap(content: string, references: Map<string, string>, ignoreInStrings: string[]): string;
|
|
3
|
+
export declare function renameResources(files: ReadonlyMap<string, string>, search: string[], replacement: string): Map<string, string>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { getLogger } from "@ui5/logger";
|
|
2
|
+
const log = getLogger("@ui5/task-adaptation::RenamingUtil");
|
|
3
|
+
export function rename(content, references, replacement) {
|
|
4
|
+
return renameMap(content, new Map(references.map(ref => [ref, replacement])), [replacement]);
|
|
5
|
+
}
|
|
6
|
+
export function renameMap(content, references, ignoreInStrings) {
|
|
7
|
+
const ignoredReferenceKeys = [];
|
|
8
|
+
for (const [key, value] of references) {
|
|
9
|
+
if (value.includes(".") !== key.includes(".")) {
|
|
10
|
+
ignoredReferenceKeys.push(key);
|
|
11
|
+
references.delete(key);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (ignoredReferenceKeys.length > 0) {
|
|
15
|
+
log.info(`Ignored renaming: ${ignoredReferenceKeys.join(", ")}`);
|
|
16
|
+
}
|
|
17
|
+
const searchTerms = [...references.keys()];
|
|
18
|
+
if (!content || !searchTerms || searchTerms.length === 0) {
|
|
19
|
+
return content;
|
|
20
|
+
}
|
|
21
|
+
const dotToSlash = (str) => str.replaceAll(".", "\/");
|
|
22
|
+
// We don't want to replace in adaptation project ids
|
|
23
|
+
ignoreInStrings.push(...ignoreInStrings, ...ignoreInStrings.map(dotToSlash));
|
|
24
|
+
let start = 0;
|
|
25
|
+
while (true) {
|
|
26
|
+
// If we don't replace some strings in the content - we find all of them
|
|
27
|
+
// and then don't replace inside their start and end indices.
|
|
28
|
+
const ignoredStrings = ignoreInStrings.map(string => {
|
|
29
|
+
return findAllOccurrences(content, string, start).map(i => ({ start: i, end: i + string.length }));
|
|
30
|
+
}).filter(arr => arr.length > 0) || [];
|
|
31
|
+
// We find the next search index with dots and slashes. Then we replace
|
|
32
|
+
// the nearest one and start search again in the next loop step.
|
|
33
|
+
const indices = new Array();
|
|
34
|
+
for (const searchTerm of searchTerms) {
|
|
35
|
+
const searchTermSlash = dotToSlash(searchTerm);
|
|
36
|
+
indices.push({
|
|
37
|
+
i: content.indexOf(searchTerm, start),
|
|
38
|
+
replacement: references.get(searchTerm),
|
|
39
|
+
searchTerm
|
|
40
|
+
}, {
|
|
41
|
+
i: content.indexOf(searchTermSlash, start),
|
|
42
|
+
replacement: dotToSlash(references.get(searchTerm)),
|
|
43
|
+
searchTerm: searchTermSlash
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const found = indices.filter(({ i }) => i > -1);
|
|
47
|
+
if (found.length === 0) {
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
50
|
+
const inBetween = (intervals, i) => {
|
|
51
|
+
for (const interval of intervals) {
|
|
52
|
+
for (const { start, end } of interval) {
|
|
53
|
+
if (i >= start && i <= end) {
|
|
54
|
+
return { start, end };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
// If we found two strings with the same index, we take the longest one
|
|
60
|
+
// to replace, e.g.: app.variant and app.variant2 has the same index,
|
|
61
|
+
// but we don't want to replace the first one with customer.app.variant,
|
|
62
|
+
// otherwise we get customer.app.variant2, which we don't need. We need
|
|
63
|
+
// to replace the whole app.variant2 with customer.app.variant.
|
|
64
|
+
const findCurrentReplace = (found) => {
|
|
65
|
+
const result = new Map();
|
|
66
|
+
for (const entry of found) {
|
|
67
|
+
const existing = result.get(entry.i);
|
|
68
|
+
if (!existing || entry.searchTerm.length >= existing.searchTerm.length) {
|
|
69
|
+
result.set(entry.i, entry);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return [...result.values()].sort((a, b) => a.i - b.i)[0];
|
|
73
|
+
};
|
|
74
|
+
// Ignore if search is in i18n key: replace "id" in "{{id.key}}" with
|
|
75
|
+
// "customer.id" and we need only the next one in string
|
|
76
|
+
found.forEach(index => index.inBetween = inBetween(ignoredStrings, index.i));
|
|
77
|
+
// There might be a situation when we found something in ignored
|
|
78
|
+
// substrings and the index could be the nearest, but after that the
|
|
79
|
+
// next index might be the actual find to replace, so we first of all
|
|
80
|
+
// ignore all the ignored substring findings and get the next actual
|
|
81
|
+
// replacement. But if there are only ignored substrings found, we take
|
|
82
|
+
// the first one just to skip it and go further.
|
|
83
|
+
const currentReplace = findCurrentReplace(found);
|
|
84
|
+
if (currentReplace.inBetween) {
|
|
85
|
+
start = currentReplace.inBetween.end;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
content = content.substring(0, currentReplace.i)
|
|
89
|
+
+ currentReplace.replacement
|
|
90
|
+
+ content.substring(currentReplace.i + currentReplace.searchTerm.length);
|
|
91
|
+
start = currentReplace.i + currentReplace.replacement.length;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function renameResources(files, search, replacement) {
|
|
96
|
+
return new Map([...files].map(([filepath, content]) => [filepath, rename(content, search, replacement)]));
|
|
97
|
+
}
|
|
98
|
+
function 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
|
+
}
|
|
110
|
+
;
|
|
111
|
+
//# sourceMappingURL=renamingUtil.js.map
|