Skip to content

Commit 55dea79

Browse files
Release 1.5.1
1 parent 8c8b26b commit 55dea79

21 files changed

+1390
-230
lines changed

package-lock.json

Lines changed: 184 additions & 217 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@
114114
"src/annotations/comparator/diffCase.ts",
115115
"src/annotations/transformers/transformer.ts",
116116
"src/model/configuration.ts",
117-
"src/model/appVariantIdHierarchyItem.ts"
117+
"src/model/appVariantIdHierarchyItem.ts",
118+
"src/adapters/adapter.ts"
118119
],
119120
"check-coverage": true,
120121
"statements": 85,

src/adapters/abapAdapter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IAdapter } from "./adapter.js";
2+
import { MergeCommandChain } from "./commands/command.js";
3+
import BaseApp from "../baseAppManager.js";
4+
import AppVariant from "../appVariantManager.js";
5+
import I18nPropertiesMergeCommand from "./commands/i18nPropertiesMergeCommand.js";
6+
7+
8+
export default class AbapAdapter implements IAdapter {
9+
createMergeCommandChain(baseApp: BaseApp, appVariant: AppVariant): MergeCommandChain {
10+
const manifestChanges = appVariant.getProcessedManifestChanges();
11+
return new MergeCommandChain([
12+
new I18nPropertiesMergeCommand(baseApp.i18nPath, appVariant.prefix, manifestChanges),
13+
]);
14+
}
15+
}

src/adapters/adapter.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import AppVariant from "../appVariantManager.js";
2+
import BaseApp from "../baseAppManager.js";
3+
import { MergeCommandChain } from "./commands/command.js";
4+
5+
export interface IAdapter {
6+
createMergeCommandChain(baseApp: BaseApp, appVariant: AppVariant): MergeCommandChain;
7+
}

src/adapters/cfAdapter.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import BaseApp from "../baseAppManager.js";
2+
import AppVariant from "../appVariantManager.js";
3+
import { IAdapter } from "./adapter.js";
4+
import { MergeCommandChain } from "./commands/command.js";
5+
import I18nPropertiesMergeCommand from "./commands/i18nPropertiesMergeCommand.js";
6+
import XsAppJsonMergeCommand from "./commands/xsAppJsonMergeCommand.js";
7+
8+
9+
export default class CFAdapter implements IAdapter {
10+
createMergeCommandChain(baseApp: BaseApp, appVariant: AppVariant): MergeCommandChain {
11+
const manifestChanges = appVariant.getProcessedManifestChanges();
12+
return new MergeCommandChain([
13+
new I18nPropertiesMergeCommand(baseApp.i18nPath, appVariant.prefix, manifestChanges),
14+
new XsAppJsonMergeCommand(),
15+
]);
16+
}
17+
}

src/adapters/commands/command.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export abstract class MergeCommand {
2+
readonly commandType = "merge";
3+
abstract accept(filename: string): boolean;
4+
/**
5+
* Executes the command on the specified file
6+
* @param files - Map of all files being processed
7+
* @param filename - The current file being processed
8+
* @param appVariantContent - Content from the app variant
9+
*/
10+
abstract execute(files: Map<string, string>, filename: string, appVariantContent: string): Promise<void>;
11+
}
12+
13+
14+
/**
15+
* CommandChain implements the Chain of Responsibility pattern
16+
* It manages a collection of commands and executes them in sequence
17+
*/
18+
export class MergeCommandChain {
19+
constructor(private commands: MergeCommand[] = []) { }
20+
21+
/**
22+
* Executes all commands in the chain that accept the given filename
23+
* @param files - Map of all files being processed
24+
* @param filename - The current file being processed
25+
* @param appVariantContent - Content from the app variant
26+
*/
27+
async execute(files: ReadonlyMap<string, string>, appVariantFiles: ReadonlyMap<string, string>): Promise<ReadonlyMap<string, string>> {
28+
const filesCopy = new Map(files);
29+
for (const [filename, appVariantContent] of appVariantFiles) {
30+
const acceptedCommands = this.commands.filter(command => command.accept(filename));
31+
if (acceptedCommands.length > 0) {
32+
for (const command of acceptedCommands) {
33+
await command.execute(filesCopy, filename, appVariantContent);
34+
}
35+
} else {
36+
filesCopy.set(filename, appVariantContent);
37+
}
38+
}
39+
return filesCopy;
40+
}
41+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { escapeRegex, trimExtension } from "../../util/commonUtil.js";
2+
import { IChange } from "../../model/types.js";
3+
import { posix as path } from "path";
4+
import { MergeCommand } from "./command.js";
5+
6+
export default class I18nPropertiesMergeCommand extends MergeCommand {
7+
8+
private mergePaths: RegExp[];
9+
private copyPaths: RegExp[];
10+
11+
constructor(private i18nPath: string, private prefix: string, manifestChanges: ReadonlyArray<IChange>) {
12+
super();
13+
const { mergePaths, copyPaths } = this.analyzeAppVariantManifestChanges(manifestChanges);
14+
this.mergePaths = mergePaths;
15+
this.copyPaths = copyPaths;
16+
}
17+
18+
accept = (filename: string) => filename.endsWith(".properties");
19+
20+
21+
private analyzeAppVariantManifestChanges(manifestChanges: ReadonlyArray<IChange>) {
22+
// check which files need to be copied and which files need to be merged and copied
23+
// this is necessary because lrep does not support multiple enhanceWith with multiple locations
24+
const TRANSLATION_REGEX_PATTERN = "((_[a-z]{2,3})?(_[a-zA-Z]{2,3}(_[a-zA-Z]{2,20})?)?)\.properties$";
25+
const mergePaths = new Set<RegExp>();
26+
const copyPaths = new Set<RegExp>();
27+
manifestChanges.forEach((change) => {
28+
const i18nPathWithExtension = change.content?.bundleUrl || change.texts?.i18n;
29+
if (i18nPathWithExtension) {
30+
// build regex to match specific + language related files
31+
const i18nPath = trimExtension(i18nPathWithExtension);
32+
const regex = new RegExp("^" + escapeRegex(i18nPath) + TRANSLATION_REGEX_PATTERN);
33+
if (change.changeType.includes("addNewModelEnhanceWith")) {
34+
copyPaths.add(regex);
35+
} else {
36+
mergePaths.add(regex);
37+
}
38+
}
39+
});
40+
return { mergePaths: Array.from(mergePaths), copyPaths: Array.from(copyPaths) };
41+
}
42+
43+
44+
async execute(files: Map<string, string>, filename: string, appVariantContent: string): Promise<void> {
45+
// merge/copy logic
46+
// check if file matches with regex in merge/copy
47+
const mergePathMatch = this.mergePaths.map(path => filename.match(path)).find(match => match);
48+
const copyPathMatch = this.copyPaths.map(path => filename.match(path)).find(match => match);
49+
if (mergePathMatch) {
50+
this.mergePropertiesFiles(files, this.i18nPath, appVariantContent, mergePathMatch[1]);
51+
}
52+
if (copyPathMatch) {
53+
files.set(path.join(this.prefix, filename), appVariantContent);
54+
}
55+
}
56+
57+
/**
58+
* Filters out specific lines from the given string.
59+
* Removes lines matching:
60+
* - __ldi.translation.uuid\s*=\s*(.*)
61+
* - ABAP_TRANSLATION
62+
* - SAPUI5 TRANSLATION-KEY
63+
*/
64+
private filterTranslationMetaLines(content: string): string {
65+
const lines = content.split('\n');
66+
const filtered = lines.filter(
67+
line =>
68+
!/^# __ldi\.translation\.uuid\s*=/.test(line) &&
69+
!line.startsWith("# ABAP_TRANSLATION") &&
70+
!line.startsWith("# SAPUI5 TRANSLATION-KEY")
71+
);
72+
return filtered.join('\n');
73+
}
74+
75+
/**
76+
* Merge/Append base property file with property file from app variant
77+
* FIXME Currently merge could duplicate keys which causes undefined
78+
* behavior => Existing keys which are in merge content must be removed =>
79+
* Actually only descriptor texts are relevant for merge which always have
80+
* app variant Id as prefix => If we filter on them we do not need to remove
81+
* existing overwritten keys (as there should be none)
82+
*/
83+
private mergePropertiesFiles(files: Map<string, string>, i18nPath: string, appVariantFileContent: string, language: string = "") {
84+
const baseAppI18nPath = i18nPath + language + ".properties";
85+
const baseAppFileContent = files.get(baseAppI18nPath);
86+
const filteredBaseContent = baseAppFileContent ? this.filterTranslationMetaLines(baseAppFileContent) : "";
87+
const content = filteredBaseContent
88+
? `${filteredBaseContent}\n\n#App variant specific text file\n\n${appVariantFileContent}`
89+
: appVariantFileContent;
90+
files.set(baseAppI18nPath, content);
91+
}
92+
93+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { merge } from "../../util/cf/xsAppJsonUtil.js";
2+
import { MergeCommand } from "./command.js";
3+
4+
export default class XsAppJsonMergeCommand extends MergeCommand {
5+
6+
accept = (filename: string) => filename === "xs-app.json";
7+
8+
async execute(files: Map<string, string>, filename: string, appVariantContent: string): Promise<void> {
9+
const baseContent = files.get(filename);
10+
const xsAppJsonFiles = [
11+
appVariantContent,
12+
baseContent // base app xs-app.json comes last
13+
].filter(file => file !== undefined) as string[];
14+
const result = merge(xsAppJsonFiles);
15+
if (result) {
16+
files.set(filename, result);
17+
}
18+
}
19+
20+
}

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { logBuilderVersion } from "./util/commonUtil.js";
44

55
import AppVariant from "./appVariantManager.js";
66
import BaseApp from "./baseAppManager.js";
7-
import I18nMerger from "./util/i18nMerger.js";
87
import { ITaskParameters } from "./model/types.js";
98
import ResourceUtil from "./util/resourceUtil.js";
109
import { determineProcessor } from "./processors/processor.js";
@@ -21,6 +20,7 @@ export default ({ workspace, options, taskUtil }: ITaskParameters) => {
2120
logBuilderVersion();
2221

2322
const processor = determineProcessor(options.configuration);
23+
const adapter = processor.getAdapter();
2424

2525
const adaptationProject = await AppVariant.fromWorkspace(workspace, options.projectNamespace);
2626
const appVariantIdHierarchy = await processor.getAppVariantIdHierarchy(adaptationProject.reference);
@@ -46,7 +46,9 @@ export default ({ workspace, options, taskUtil }: ITaskParameters) => {
4646
}
4747
appVariants.push(appVariant);
4848
const adaptedFiles = await baseApp.adapt(appVariant, processor);
49-
return I18nMerger.merge(adaptedFiles, baseApp.i18nPath, appVariant);
49+
const mergeCommandChain = adapter.createMergeCommandChain(baseApp, appVariant);
50+
const mergedFiles = await mergeCommandChain.execute(adaptedFiles, appVariant.getProcessedFiles());
51+
return mergedFiles;
5052
}
5153

5254
let files = await fetchFilesPromises.reduce(async (previousFiles, currentFiles) =>

src/processors/abapProcessor.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import IProcessor from "./processor.js";
66
import Language from "../model/language.js";
77
import { cached } from "../cache/cacheHolder.js";
88
import { validateObject } from "../util/commonUtil.js";
9+
import { IAdapter } from "../adapters/adapter.js";
10+
import AbapAdapter from "../adapters/abapAdapter.js";
911

1012
export default class AbapProcessor implements IProcessor {
1113

@@ -26,6 +28,10 @@ export default class AbapProcessor implements IProcessor {
2628
return this.abapRepoManager.getAppVariantIdHierarchy(appId);
2729
}
2830

31+
getAdapter(): IAdapter {
32+
return new AbapAdapter();
33+
}
34+
2935

3036
@cached()
3137
fetch(repoName: string, _cachebusterToken: string): Promise<Map<string, string>> {

0 commit comments

Comments
 (0)