@sap-ux/fe-fpm-writer 0.42.19 → 0.42.20

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.
@@ -73,10 +73,9 @@ const PLACEHOLDERS = {
73
73
  async function generateBuildingBlock(basePath, config, fs) {
74
74
  const { viewOrFragmentPath, aggregationPath, buildingBlockData, allowAutoAddDependencyLib = true } = config;
75
75
  // Validate the base and view paths
76
- if (!fs) {
77
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
78
- }
76
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
79
77
  await (0, validate_1.validateBasePath)(basePath, fs, []);
78
+ const fnGenerateId = config.buildingBlockData.generateId ?? (await (0, file_1.createIdGenerator)(basePath, fs));
80
79
  if (!fs.exists((0, node_path_1.join)(basePath, viewOrFragmentPath))) {
81
80
  throw new Error(`Invalid view path ${viewOrFragmentPath}.`);
82
81
  }
@@ -88,7 +87,7 @@ async function generateBuildingBlock(basePath, config, fs) {
88
87
  hasAggregation,
89
88
  aggregationNamespace
90
89
  };
91
- const templateDocument = getTemplateDocument(processedBuildingBlockData, xmlDocument, fs, manifest, templateConfig);
90
+ const templateDocument = getTemplateDocument({ ...processedBuildingBlockData, generateId: fnGenerateId }, xmlDocument, fs, manifest, templateConfig);
92
91
  if (buildingBlockData.buildingBlockType === types_1.BuildingBlockType.RichTextEditor ||
93
92
  buildingBlockData.buildingBlockType === types_1.BuildingBlockType.RichTextEditorButtonGroups) {
94
93
  const minUI5Version = manifest ? (0, semver_1.coerce)((0, project_access_1.getMinimumUI5Version)(manifest)) : undefined;
@@ -235,11 +234,18 @@ function getTemplateContent(buildingBlockData, viewDocument, manifest, fs, usePl
235
234
  if (!buildingBlockData.id) {
236
235
  buildingBlockData.id = PLACEHOLDERS.id;
237
236
  }
238
- return (0, ejs_1.render)(fs.read(templateFilePath), {
237
+ const configKey = (0, file_1.getRelativeTemplateComponentPath)(templateFilePath);
238
+ const config = file_1.CONFIG[configKey];
239
+ let context = {
239
240
  macrosNamespace: viewDocument ? (0, xml_1.getOrAddNamespace)(viewDocument, 'sap.fe.macros', 'macros') : 'macros',
240
241
  data: buildingBlockData,
241
242
  config: templateConfig
242
- }, {});
243
+ };
244
+ if (config?.getData) {
245
+ const additionalContext = config.getData(buildingBlockData.generateId, buildingBlockData);
246
+ context = { ...context, ...additionalContext };
247
+ }
248
+ return (0, ejs_1.render)(fs.read(templateFilePath), context, {});
243
249
  }
244
250
  /**
245
251
  * Method returns the manifest content for the required dependency library.
@@ -42,6 +42,7 @@ const templates_1 = require("../templates");
42
42
  const event_handler_1 = require("../common/event-handler");
43
43
  const defaults_1 = require("../common/defaults");
44
44
  const xml_1 = require("./prompts/utils/xml");
45
+ const file_1 = require("../common/file");
45
46
  /**
46
47
  * Button group configurations used for validation and providing available button groups.
47
48
  */
@@ -124,7 +125,7 @@ function processCustomColumn(buildingBlockData, context) {
124
125
  });
125
126
  columnConfig.eventHandler = processedEventHandler;
126
127
  }
127
- columnConfig.content = (0, defaults_1.getDefaultFragmentContent)('Sample Text', processedEventHandler);
128
+ columnConfig.content = (0, defaults_1.getDefaultFragmentContent)('Sample Text', buildingBlockData.generateId, processedEventHandler);
128
129
  if (viewPath && !fs.exists(viewPath)) {
129
130
  fs.copyTpl((0, templates_1.getTemplatePath)(config.templateFile), viewPath, columnConfig);
130
131
  }
@@ -153,7 +154,7 @@ function processCustomFilterField(buildingBlockData, context) {
153
154
  throw new Error('EmbeddedFragment is required for CustomFilterField');
154
155
  }
155
156
  const config = getBuildingBlockConfig(types_1.BuildingBlockType.CustomFilterField);
156
- const filterConfig = {
157
+ let filterConfig = {
157
158
  label: buildingBlockData.label,
158
159
  property: buildingBlockData.property,
159
160
  required: buildingBlockData.required ?? false,
@@ -171,6 +172,12 @@ function processCustomFilterField(buildingBlockData, context) {
171
172
  templatePath: 'filter/Controller'
172
173
  });
173
174
  }
175
+ const configKey = config.templateFile;
176
+ const additionnalDataConfig = file_1.CONFIG[configKey];
177
+ if (additionnalDataConfig?.getData) {
178
+ const additionalContext = additionnalDataConfig.getData(buildingBlockData.generateId, buildingBlockData);
179
+ filterConfig = { ...filterConfig, ...additionalContext };
180
+ }
174
181
  if (viewPath && !fs.exists(viewPath)) {
175
182
  fs.copyTpl((0, templates_1.getTemplatePath)(config.templateFile), viewPath, filterConfig);
176
183
  }
@@ -15,11 +15,11 @@ exports.getBuildingBlockIdPrompt = getBuildingBlockIdPrompt;
15
15
  const node_path_1 = require("node:path");
16
16
  const service_1 = require("./service");
17
17
  const project_access_1 = require("@sap-ux/project-access");
18
- const file_1 = require("@sap-ux/project-access/dist/file");
19
18
  const types_1 = require("../../types");
20
19
  const xml_1 = require("./xml");
21
20
  const i18n_1 = require("../../../i18n");
22
21
  const prompt_helpers_1 = require("./prompt-helpers");
22
+ const file_1 = require("../../../common/file");
23
23
  /* eslint-disable @typescript-eslint/no-floating-promises */
24
24
  (0, i18n_1.initI18n)();
25
25
  const t = (0, i18n_1.translate)(i18n_1.i18nNamespaces.buildingBlock, 'prompts.common.');
@@ -101,11 +101,8 @@ function getViewOrFragmentPathPrompt(context, validationErrorMessage, properties
101
101
  name: 'viewOrFragmentPath',
102
102
  choices: project
103
103
  ? async () => {
104
- const files = await (0, file_1.findFilesByExtension)('.xml', appPath, ['.git', 'node_modules', 'dist', 'annotations', 'localService'], fs);
105
- const lookupFiles = ['.fragment.xml', '.view.xml'];
106
- return transformChoices(files
107
- .filter((fileName) => lookupFiles.some((lookupFile) => fileName.endsWith(lookupFile)))
108
- .map((file) => (0, node_path_1.relative)(appPath, file)));
104
+ const fragmentAndViewFiles = await (0, file_1.getFragmentAndViewFiles)(appPath, fs);
105
+ return transformChoices(fragmentAndViewFiles.map((file) => (0, node_path_1.relative)(appPath, file)));
109
106
  }
110
107
  : [],
111
108
  validate: (value) => (!project || value ? true : validationErrorMessage),
@@ -1,3 +1,4 @@
1
+ import type { IdGeneratorFunction } from '../common/file';
1
2
  import type { CustomElement, CustomFragment, EventHandler, FragmentContentData, Position } from '../common/types';
2
3
  /**
3
4
  * Building block type.
@@ -63,6 +64,13 @@ export interface BuildingBlock {
63
64
  * Defines the relative path of the property in the metamodel, based on the current contextPath.
64
65
  */
65
66
  metaPath?: string | BuildingBlockMetaPath;
67
+ /**
68
+ * Generates a unique ID for the building block based on the provided base ID.
69
+ *
70
+ * @param baseId - The base ID to generate from, usually related to the building block type.
71
+ * @returns A unique ID string.
72
+ */
73
+ generateId: IdGeneratorFunction;
66
74
  }
67
75
  /**
68
76
  * Represents a Chart building block control.
@@ -38,9 +38,10 @@ function getManifestRoot(ui5Version) {
38
38
  * @param {CustomTableColumn} data - a custom column configuration object
39
39
  * @param {string} manifestPath - path to the project's manifest.json
40
40
  * @param {Manifest} manifest - the application manifest
41
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
41
42
  * @returns enhanced configuration
42
43
  */
43
- function enhanceConfig(fs, data, manifestPath, manifest) {
44
+ function enhanceConfig(fs, data, manifestPath, manifest, generateId) {
44
45
  // clone input and set defaults
45
46
  const config = { ...data };
46
47
  (0, defaults_1.setCommonDefaults)(config, manifestPath, manifest);
@@ -55,7 +56,7 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
55
56
  const content = config.properties && config.properties.length > 0
56
57
  ? `{=%{${config.properties.join("} + ' ' + %{")}}}`
57
58
  : 'Sample Text';
58
- config.content = config.control || (0, defaults_1.getDefaultFragmentContent)(content, config.eventHandler);
59
+ config.content = config.control || (0, defaults_1.getDefaultFragmentContent)(content, generateId, config.eventHandler);
59
60
  return config;
60
61
  }
61
62
  /**
@@ -68,17 +69,16 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
68
69
  */
69
70
  async function generateCustomColumn(basePath, customColumn, fs) {
70
71
  (0, validate_1.validateVersion)(customColumn.minUI5Version);
71
- if (!fs) {
72
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
73
- }
72
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
74
73
  await (0, validate_1.validateBasePath)(basePath, fs);
74
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fs);
75
75
  const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
76
76
  // merge with defaults
77
- const completeColumn = enhanceConfig(fs, customColumn, manifestPath, manifest);
77
+ const completeColumn = enhanceConfig(fs, customColumn, manifestPath, manifest, fnGenerateId);
78
78
  // add fragment
79
79
  const viewPath = (0, node_path_1.join)(completeColumn.path, `${completeColumn.fragmentFile ?? completeColumn.name}.fragment.xml`);
80
80
  if (completeColumn.control || !fs.exists(viewPath)) {
81
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeColumn);
81
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeColumn, fnGenerateId);
82
82
  }
83
83
  // enhance manifest with column definition
84
84
  const manifestRoot = getManifestRoot(customColumn.minUI5Version);
@@ -1,3 +1,4 @@
1
+ import type { IdGeneratorFunction } from './file';
1
2
  import type { CustomElement, FragmentContentData, InternalCustomElement, Manifest } from './types';
2
3
  export declare const FCL_ROUTER = "sap.f.routing.Router";
3
4
  /**
@@ -13,6 +14,7 @@ export declare function setCommonDefaults<T extends CustomElement & Partial<Inte
13
14
  * Method to generate default content data for xml fragment.
14
15
  *
15
16
  * @param {string} text - text of button or label
17
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
16
18
  * @param {string} [eventHandler] - event handler path
17
19
  * if value is passed then "Button" control with 'press' event would be generated
18
20
  * if value is not passed then "Text" control would be generated
@@ -21,11 +23,12 @@ export declare function setCommonDefaults<T extends CustomElement & Partial<Inte
21
23
  * @param {boolean} includeRequireInContent - controls if `core:require` attribute should be included to fragment content
22
24
  * @returns default content for fragment
23
25
  */
24
- export declare function getDefaultFragmentContentData(text: string, eventHandler?: string, isController?: boolean, prefferInput?: boolean, includeRequireInContent?: boolean): FragmentContentData;
26
+ export declare function getDefaultFragmentContentData(text: string, generateId: IdGeneratorFunction, eventHandler?: string, isController?: boolean, prefferInput?: boolean, includeRequireInContent?: boolean): FragmentContentData;
25
27
  /**
26
28
  * Method to generate default content for xml fragment.
27
29
  *
28
30
  * @param {string} text - text of button or label
31
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
29
32
  * @param {string} [eventHandler] - event handler path
30
33
  * if value is passed then "Button" control with 'press' event would be generated
31
34
  * if value is not passed then "Text" control would be generated
@@ -33,5 +36,5 @@ export declare function getDefaultFragmentContentData(text: string, eventHandler
33
36
  * @param {boolean} prefferInput - controls if `input` element should be added to default fragment content
34
37
  * @returns default content for fragment
35
38
  */
36
- export declare function getDefaultFragmentContent(text: string, eventHandler?: string, isController?: boolean, prefferInput?: boolean): string;
39
+ export declare function getDefaultFragmentContent(text: string, generateId: IdGeneratorFunction, eventHandler?: string, isController?: boolean, prefferInput?: boolean): string;
37
40
  //# sourceMappingURL=defaults.d.ts.map
@@ -26,6 +26,7 @@ function setCommonDefaults(config, manifestPath, manifest) {
26
26
  * Method to generate default content data for xml fragment.
27
27
  *
28
28
  * @param {string} text - text of button or label
29
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
29
30
  * @param {string} [eventHandler] - event handler path
30
31
  * if value is passed then "Button" control with 'press' event would be generated
31
32
  * if value is not passed then "Text" control would be generated
@@ -34,7 +35,7 @@ function setCommonDefaults(config, manifestPath, manifest) {
34
35
  * @param {boolean} includeRequireInContent - controls if `core:require` attribute should be included to fragment content
35
36
  * @returns default content for fragment
36
37
  */
37
- function getDefaultFragmentContentData(text, eventHandler, isController = false, prefferInput = false, includeRequireInContent = true) {
38
+ function getDefaultFragmentContentData(text, generateId, eventHandler, isController = false, prefferInput = false, includeRequireInContent = true) {
38
39
  let content;
39
40
  let requireAttribute;
40
41
  if (eventHandler) {
@@ -49,19 +50,23 @@ function getDefaultFragmentContentData(text, eventHandler, isController = false,
49
50
  if (prefferInput) {
50
51
  attributes.push(`value="${text}"`);
51
52
  attributes.push(`change="handler.${method}"`);
52
- content = `<Input ${attributes.join(' ')} />`;
53
+ const id = generateId('Input');
54
+ content = `<Input id="${id}" ${attributes.join(' ')} />`;
53
55
  }
54
56
  else {
55
57
  attributes.push(`text="${text}"`);
56
58
  attributes.push(`press="handler.${method}"`);
57
- content = `<Button ${attributes.join(' ')} />`;
59
+ const id = generateId('Button');
60
+ content = `<Button id="${id}" ${attributes.join(' ')} />`;
58
61
  }
59
62
  }
60
63
  else if (prefferInput) {
61
- content = `<Input value="${text}" />`;
64
+ const id = generateId('Input');
65
+ content = `<Input id="${id}" value="${text}" />`;
62
66
  }
63
67
  else {
64
- content = `<Text text="${text}" />`;
68
+ const id = generateId('Text');
69
+ content = `<Text id="${id}" text="${text}" />`;
65
70
  }
66
71
  return {
67
72
  content,
@@ -72,6 +77,7 @@ function getDefaultFragmentContentData(text, eventHandler, isController = false,
72
77
  * Method to generate default content for xml fragment.
73
78
  *
74
79
  * @param {string} text - text of button or label
80
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
75
81
  * @param {string} [eventHandler] - event handler path
76
82
  * if value is passed then "Button" control with 'press' event would be generated
77
83
  * if value is not passed then "Text" control would be generated
@@ -79,8 +85,8 @@ function getDefaultFragmentContentData(text, eventHandler, isController = false,
79
85
  * @param {boolean} prefferInput - controls if `input` element should be added to default fragment content
80
86
  * @returns default content for fragment
81
87
  */
82
- function getDefaultFragmentContent(text, eventHandler, isController = false, prefferInput = false) {
83
- const contentData = getDefaultFragmentContentData(text, eventHandler, isController, prefferInput);
88
+ function getDefaultFragmentContent(text, generateId, eventHandler, isController = false, prefferInput = false) {
89
+ const contentData = getDefaultFragmentContentData(text, generateId, eventHandler, isController, prefferInput);
84
90
  return contentData.content;
85
91
  }
86
92
  //# sourceMappingURL=defaults.js.map
@@ -1,5 +1,76 @@
1
1
  import type { CopyOptions, Editor } from 'mem-fs-editor';
2
2
  import type { TabInfo } from '../common/types';
3
+ interface ButtonGroup {
4
+ name: string;
5
+ buttons: string[];
6
+ visible?: boolean;
7
+ priority?: number;
8
+ customToolbarPriority?: number;
9
+ row?: number;
10
+ id?: string;
11
+ }
12
+ export type IdGeneratorFunction = (baseId: string, validatedIds?: string[]) => string;
13
+ interface TemplateContext {
14
+ buttonGroups?: ButtonGroup[];
15
+ name?: string;
16
+ data?: {
17
+ buttonGroups?: ButtonGroup[];
18
+ };
19
+ }
20
+ export declare const CONFIG: {
21
+ "page/custom/1.94/ext/View.xml": {
22
+ getData: (generateId: IdGeneratorFunction, context?: TemplateContext) => {
23
+ ids: Record<string, string>;
24
+ };
25
+ };
26
+ "page/custom/1.84/ext/View.xml": {
27
+ getData: (generateId: IdGeneratorFunction, context?: TemplateContext) => {
28
+ ids: Record<string, string>;
29
+ };
30
+ };
31
+ "common/FragmentWithForm.xml": {
32
+ getData: (generateId: IdGeneratorFunction) => {
33
+ ids: Record<string, string>;
34
+ };
35
+ };
36
+ "common/FragmentWithVBox.xml": {
37
+ getData: (generateId: IdGeneratorFunction) => {
38
+ ids: Record<string, string>;
39
+ };
40
+ };
41
+ "view/ext/CustomViewWithTable.xml": {
42
+ getData: (generateId: IdGeneratorFunction) => {
43
+ ids: Record<string, string>;
44
+ };
45
+ };
46
+ "filter/fragment.xml": {
47
+ getData: (generateId: IdGeneratorFunction) => {
48
+ ids: Record<string, string>;
49
+ };
50
+ };
51
+ "building-block/rich-text-editor-button-groups/View.xml": {
52
+ getData: (generateId: IdGeneratorFunction, context?: TemplateContext) => {
53
+ ids: Record<string, string>;
54
+ };
55
+ };
56
+ };
57
+ /**
58
+ * Retrieves all view and fragment files in the application.
59
+ *
60
+ * @param appPath - The root path of the application
61
+ * @param fs - The file system object for reading files
62
+ * @returns A list of view and fragment files
63
+ */
64
+ export declare function getFragmentAndViewFiles(appPath: string, fs: Editor): Promise<string[]>;
65
+ /**
66
+ * Creates an ID generator function for a given base path and editor.
67
+ * The generator ensures unique IDs across all fragment and view files in the project.
68
+ *
69
+ * @param basePath - Base path of the project
70
+ * @param fsEditor - mem-fs-editor instance
71
+ * @returns A function that generates unique IDs based on a base ID string
72
+ */
73
+ export declare function createIdGenerator(basePath: string | undefined, fsEditor: Editor): Promise<(baseId: string) => string>;
3
74
  export declare const COPY_TEMPLATE_OPTIONS: CopyOptions & {
4
75
  noGlob: boolean;
5
76
  };
@@ -44,7 +115,16 @@ export declare function extendJSON(fs: Editor, params: ExtendJsonParams): void;
44
115
  * @param from - Source path of the template file or directory.
45
116
  * @param to - Destination path where the rendered files will be written.
46
117
  * @param context - Optional template context used for interpolation.
118
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
119
+ */
120
+ export declare function copyTpl(fs: Editor, from: string, to: string, context?: object, generateId?: (baseId: string) => string): void;
121
+ /**
122
+ * Extracts the relative path from the templates directory.
123
+ * Works cross-platform by normalizing path separators.
124
+ *
125
+ * @param absolutePath - Absolute path to a template file or directory
126
+ * @returns Relative path from templates directory with forward slashes, or original path if 'templates' not found
47
127
  */
48
- export declare function copyTpl(fs: Editor, from: string, to: string, context?: object): void;
128
+ export declare function getRelativeTemplateComponentPath(absolutePath: string): string;
49
129
  export {};
50
130
  //# sourceMappingURL=file.d.ts.map
@@ -1,12 +1,151 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.COPY_TEMPLATE_OPTIONS = void 0;
3
+ exports.COPY_TEMPLATE_OPTIONS = exports.CONFIG = void 0;
4
+ exports.getFragmentAndViewFiles = getFragmentAndViewFiles;
5
+ exports.createIdGenerator = createIdGenerator;
4
6
  exports.detectTabSpacing = detectTabSpacing;
5
7
  exports.getJsonSpace = getJsonSpace;
6
8
  exports.extendJSON = extendJSON;
7
9
  exports.copyTpl = copyTpl;
10
+ exports.getRelativeTemplateComponentPath = getRelativeTemplateComponentPath;
11
+ const node_path_1 = require("node:path");
12
+ const file_1 = require("@sap-ux/project-access/dist/file");
13
+ const utils_1 = require("../building-block/prompts/utils");
8
14
  const CHAR_SPACE = ' ';
9
15
  const CHAR_TAB = '\t';
16
+ exports.CONFIG = {
17
+ ['page/custom/1.94/ext/View.xml']: {
18
+ getData: (generateId, context) => {
19
+ return {
20
+ ids: {
21
+ page: generateId(context?.name ?? 'Page')
22
+ }
23
+ };
24
+ }
25
+ },
26
+ ['page/custom/1.84/ext/View.xml']: {
27
+ getData: (generateId, context) => {
28
+ return {
29
+ ids: {
30
+ page: generateId(context?.name ?? 'Page')
31
+ }
32
+ };
33
+ }
34
+ },
35
+ ['common/FragmentWithForm.xml']: {
36
+ getData: (generateId) => {
37
+ return {
38
+ ids: {
39
+ formElement: generateId('FormElement')
40
+ }
41
+ };
42
+ }
43
+ },
44
+ ['common/FragmentWithVBox.xml']: {
45
+ getData: (generateId) => {
46
+ return {
47
+ ids: {
48
+ vbox: generateId('VBox')
49
+ }
50
+ };
51
+ }
52
+ },
53
+ ['view/ext/CustomViewWithTable.xml']: {
54
+ getData: (generateId) => {
55
+ return {
56
+ ids: {
57
+ table: generateId('Table')
58
+ }
59
+ };
60
+ }
61
+ },
62
+ ['filter/fragment.xml']: {
63
+ getData: (generateId) => {
64
+ const item1 = generateId('Item');
65
+ const item2 = generateId('Item', [item1]);
66
+ const item3 = generateId('Item', [item1, item2]);
67
+ return {
68
+ ids: {
69
+ comboBox: generateId('ComboBox'),
70
+ item1,
71
+ item2,
72
+ item3
73
+ }
74
+ };
75
+ }
76
+ },
77
+ ['building-block/rich-text-editor-button-groups/View.xml']: {
78
+ getData: (generateId, context) => {
79
+ // Get buttonGroups from context
80
+ const buttonGroups = context?.buttonGroups || context?.data?.buttonGroups || [];
81
+ // Generate IDs for each button group and store in ids object
82
+ const ids = {};
83
+ const validatedIds = [];
84
+ buttonGroups.forEach((group, index) => {
85
+ const id = generateId(`${'ButtonGroup'}`, validatedIds);
86
+ ids[index] = group.id ?? id;
87
+ if (!group.id) {
88
+ validatedIds.push(id);
89
+ }
90
+ });
91
+ return { ids };
92
+ }
93
+ }
94
+ };
95
+ /**
96
+ * Generates a unique element ID that is not already used in any view or fragment file.
97
+ * Uses an incremental counter for predictable, readable IDs.
98
+ *
99
+ * @param fs - The file system object for reading files
100
+ * @param baseId - The base name for the ID (e.g., 'filterBar', 'chart')
101
+ * @param filteredFiles - The list of files to check for ID availability
102
+ * @param validatedIds - A list of IDs that have already been validated in the current session to avoid duplicates
103
+ * @returns A unique ID that is available across all view and fragment files
104
+ */
105
+ function generateUniqueElementId(fs, baseId, filteredFiles, validatedIds = []) {
106
+ const maxAttempts = 1000;
107
+ if (filteredFiles.every((file) => (0, utils_1.isElementIdAvailable)(fs, file, baseId)) && !validatedIds.includes(baseId)) {
108
+ return baseId;
109
+ }
110
+ for (let counter = 1; counter < maxAttempts; counter++) {
111
+ const candidateId = `${baseId}${counter}`;
112
+ if (filteredFiles.every((file) => (0, utils_1.isElementIdAvailable)(fs, file, candidateId)) &&
113
+ !validatedIds.includes(candidateId)) {
114
+ return candidateId;
115
+ }
116
+ }
117
+ // If we couldn't find an available ID after maxAttempts
118
+ throw new Error(`Failed to generate unique ID for base '${baseId}' after ${maxAttempts} attempts`);
119
+ }
120
+ /**
121
+ * Retrieves all view and fragment files in the application.
122
+ *
123
+ * @param appPath - The root path of the application
124
+ * @param fs - The file system object for reading files
125
+ * @returns A list of view and fragment files
126
+ */
127
+ async function getFragmentAndViewFiles(appPath, fs) {
128
+ const files = await (0, file_1.findFilesByExtension)('.xml', appPath, ['.git', 'node_modules', 'dist', 'annotations', 'localService'], fs);
129
+ const lookupFiles = ['.fragment.xml', '.view.xml'];
130
+ return files.filter((fileName) => lookupFiles.some((lookupFile) => fileName.endsWith(lookupFile)));
131
+ }
132
+ /**
133
+ * Creates an ID generator function for a given base path and editor.
134
+ * The generator ensures unique IDs across all fragment and view files in the project.
135
+ *
136
+ * @param basePath - Base path of the project
137
+ * @param fsEditor - mem-fs-editor instance
138
+ * @returns A function that generates unique IDs based on a base ID string
139
+ */
140
+ async function createIdGenerator(basePath, fsEditor) {
141
+ let files = [];
142
+ if (basePath) {
143
+ files = await getFragmentAndViewFiles(basePath, fsEditor);
144
+ }
145
+ return (baseId, validatedIds = []) => {
146
+ return generateUniqueElementId(fsEditor, baseId, files, validatedIds);
147
+ };
148
+ }
10
149
  // `noGlob` is supported in `mem-fs-editor` v9,
11
150
  // but is missing from `@types/mem-fs-editor` (no v9 typings), so we extend the type here.
12
151
  exports.COPY_TEMPLATE_OPTIONS = {
@@ -102,8 +241,34 @@ function extendJSON(fs, params) {
102
241
  * @param from - Source path of the template file or directory.
103
242
  * @param to - Destination path where the rendered files will be written.
104
243
  * @param context - Optional template context used for interpolation.
244
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
105
245
  */
106
- function copyTpl(fs, from, to, context) {
246
+ function copyTpl(fs, from, to, context, generateId) {
247
+ const configKey = getRelativeTemplateComponentPath(from);
248
+ const config = exports.CONFIG[configKey];
249
+ if (generateId && config?.getData) {
250
+ const additionalContext = config.getData(generateId, context);
251
+ context = { ...context, ...additionalContext };
252
+ }
107
253
  fs.copyTpl(from, to, context, undefined, exports.COPY_TEMPLATE_OPTIONS);
108
254
  }
255
+ /**
256
+ * Extracts the relative path from the templates directory.
257
+ * Works cross-platform by normalizing path separators.
258
+ *
259
+ * @param absolutePath - Absolute path to a template file or directory
260
+ * @returns Relative path from templates directory with forward slashes, or original path if 'templates' not found
261
+ */
262
+ function getRelativeTemplateComponentPath(absolutePath) {
263
+ const normalizedPath = (0, node_path_1.normalize)(absolutePath);
264
+ const templatesMarker = `${node_path_1.sep}templates${node_path_1.sep}`;
265
+ const templatesIndex = normalizedPath.indexOf(templatesMarker);
266
+ if (templatesIndex === -1) {
267
+ return absolutePath;
268
+ }
269
+ // Extract everything after '/templates/' or '\\templates\\'
270
+ const relativePath = normalizedPath.substring(templatesIndex + templatesMarker.length);
271
+ // Normalize to forward slashes for consistency
272
+ return relativePath.split(node_path_1.sep).join('/');
273
+ }
109
274
  //# sourceMappingURL=file.js.map
@@ -18,9 +18,10 @@ const utils_1 = require("../common/utils");
18
18
  * @param {CustomField} data - a custom field configuration object
19
19
  * @param {string} manifestPath - path to the project's manifest.json
20
20
  * @param {Manifest} manifest - the application manifest
21
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
21
22
  * @returns enhanced configuration
22
23
  */
23
- function enhanceConfig(fs, data, manifestPath, manifest) {
24
+ function enhanceConfig(fs, data, manifestPath, manifest, generateId) {
24
25
  // clone input and set defaults
25
26
  const config = { ...data };
26
27
  (0, defaults_1.setCommonDefaults)(config, manifestPath, manifest);
@@ -36,7 +37,7 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
36
37
  config.content = config.control;
37
38
  }
38
39
  else {
39
- Object.assign(config, (0, defaults_1.getDefaultFragmentContentData)(config.name, config.eventHandler));
40
+ Object.assign(config, (0, defaults_1.getDefaultFragmentContentData)(config.name, generateId, config.eventHandler));
40
41
  }
41
42
  return config;
42
43
  }
@@ -50,17 +51,16 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
50
51
  */
51
52
  async function generateCustomField(basePath, customField, fs) {
52
53
  (0, validate_1.validateVersion)(customField.minUI5Version);
53
- if (!fs) {
54
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
55
- }
54
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
56
55
  await (0, validate_1.validateBasePath)(basePath, fs);
56
+ const generateId = await (0, file_1.createIdGenerator)(basePath, fs);
57
57
  const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
58
58
  // merge with defaults
59
- const completeField = enhanceConfig(fs, customField, manifestPath, manifest);
59
+ const completeField = enhanceConfig(fs, customField, manifestPath, manifest, generateId);
60
60
  // add fragment
61
61
  const viewPath = (0, node_path_1.join)(completeField.path, `${completeField.fragmentFile ?? completeField.name}.fragment.xml`);
62
62
  if (!fs.exists(viewPath)) {
63
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeField);
63
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeField, generateId);
64
64
  }
65
65
  // enhance manifest with field definition
66
66
  const templatePath = (0, templates_1.getTemplatePath)('/field/manifest.json');
@@ -48,6 +48,7 @@ async function generateCustomFilter(basePath, filterConfig, fs) {
48
48
  fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
49
49
  }
50
50
  await (0, validate_1.validateBasePath)(basePath, fs);
51
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fs);
51
52
  const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
52
53
  const config = enhanceConfig(filterConfig, manifestPath, manifest);
53
54
  // Apply event handler
@@ -65,7 +66,7 @@ async function generateCustomFilter(basePath, filterConfig, fs) {
65
66
  // create a fragment file
66
67
  const fragmentPath = (0, node_path_1.join)(config.path, `${config.fragmentFile}.fragment.xml`);
67
68
  if (!fs.exists(fragmentPath)) {
68
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)(`filter/fragment.xml`), fragmentPath, config);
69
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)(`filter/fragment.xml`), fragmentPath, config, fnGenerateId);
69
70
  }
70
71
  return fs;
71
72
  }
@@ -17,7 +17,7 @@ const utils_1 = require("../common/utils");
17
17
  const file_1 = require("../common/file");
18
18
  const building_block_1 = require("../building-block");
19
19
  const types_2 = require("../building-block/types");
20
- const xml_1 = require("../building-block/prompts/utils/xml");
20
+ const utils_2 = require("../building-block/prompts/utils");
21
21
  const i18n_1 = require("../i18n");
22
22
  /**
23
23
  * Enhances the provided custom page configuration with default data.
@@ -80,23 +80,26 @@ function getTemplateRoot(ui5Version) {
80
80
  * @param data.minUI5Version
81
81
  * @param {string} viewPath - The path to the view XML file.
82
82
  * @param {Editor} fs - The memfs editor instance.
83
+ * @param {(baseId: string) => Promise<string>} generateId - Function to generate unique IDs for the building block elements.
83
84
  * @param {Logger} [log] - Logger instance.
84
85
  * @returns {Promise<void>} Resolves when the building block is handled or skipped due to version constraints.
85
86
  */
86
- async function handlePageBuildingBlock(basePath, data, viewPath, fs, log) {
87
+ async function handlePageBuildingBlock(basePath, data, viewPath, fs, generateId, log) {
87
88
  const minVersion = (0, semver_1.coerce)(data.minUI5Version);
88
89
  const t = (0, i18n_1.translate)(i18n_1.i18nNamespaces.buildingBlock, 'pageBuildingBlock.');
89
90
  if (minVersion && (0, semver_1.lt)(minVersion.version, '1.136.0')) {
90
91
  log?.warn(t('minUi5VersionRequirement', { minUI5Version: data.minUI5Version }));
91
92
  return;
92
93
  }
94
+ const pageId = await generateId('Page');
93
95
  await (0, building_block_1.generateBuildingBlock)(basePath, {
94
96
  viewOrFragmentPath: (0, node_path_1.relative)(basePath, viewPath),
95
- aggregationPath: (0, xml_1.augmentXpathWithLocalNames)(`/mvc:View/Page`),
97
+ aggregationPath: (0, utils_2.augmentXpathWithLocalNames)(`/mvc:View/Page`),
96
98
  replace: true,
97
99
  buildingBlockData: {
98
- id: 'Page',
100
+ id: pageId,
99
101
  buildingBlockType: types_2.BuildingBlockType.Page,
102
+ generateId,
100
103
  title: data.pageBuildingBlockTitle
101
104
  }
102
105
  }, fs);
@@ -111,12 +114,11 @@ async function handlePageBuildingBlock(basePath, data, viewPath, fs, log) {
111
114
  * @returns {Promise<Editor>} the updated memfs editor instance
112
115
  */
113
116
  async function generate(basePath, data, fs, log) {
114
- if (!fs) {
115
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
116
- }
117
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
117
118
  (0, validate_1.validateVersion)(data.minUI5Version);
118
119
  await (0, common_1.validatePageConfig)(basePath, data, fs, []);
119
120
  const manifestPath = await (0, utils_1.getManifestPath)(basePath, fs);
121
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fs);
120
122
  const config = enhanceData(data, manifestPath, fs);
121
123
  // merge content into existing files
122
124
  const root = getTemplateRoot(data.minUI5Version);
@@ -130,7 +132,7 @@ async function generate(basePath, data, fs, log) {
130
132
  // add extension content
131
133
  const viewPath = (0, node_path_1.join)(config.path, `${config.name}.view.xml`);
132
134
  if (!fs.exists(viewPath)) {
133
- (0, file_1.copyTpl)(fs, (0, node_path_1.join)(root, 'ext/View.xml'), viewPath, config);
135
+ (0, file_1.copyTpl)(fs, (0, node_path_1.join)(root, 'ext/View.xml'), viewPath, config, fnGenerateId);
134
136
  // i18n.properties
135
137
  const manifest = fs.readJSON(manifestPath);
136
138
  const defaultI18nPath = 'i18n/i18n.properties';
@@ -145,7 +147,7 @@ async function generate(basePath, data, fs, log) {
145
147
  }
146
148
  }
147
149
  if (data.pageBuildingBlockTitle) {
148
- await handlePageBuildingBlock(basePath, { pageBuildingBlockTitle: data.pageBuildingBlockTitle, minUI5Version: data.minUI5Version }, viewPath, fs, log);
150
+ await handlePageBuildingBlock(basePath, { pageBuildingBlockTitle: data.pageBuildingBlockTitle, minUI5Version: data.minUI5Version }, viewPath, fs, fnGenerateId, log);
149
151
  }
150
152
  const ext = data.typescript ? 'ts' : 'js';
151
153
  const controllerPath = (0, node_path_1.join)(config.path, `${config.name}.controller.${ext}`);
@@ -10,7 +10,7 @@ export declare class PromptsAPI {
10
10
  context: PromptContext;
11
11
  private cache;
12
12
  /**
13
- * Contructore of prompt API.
13
+ * Constructor of prompt API.
14
14
  *
15
15
  * @param fs the file system object for reading files
16
16
  * @param project
@@ -20,7 +20,7 @@ class PromptsAPI {
20
20
  // Cached questions
21
21
  cache = {};
22
22
  /**
23
- * Contructore of prompt API.
23
+ * Constructor of prompt API.
24
24
  *
25
25
  * @param fs the file system object for reading files
26
26
  * @param project
@@ -46,9 +46,7 @@ class PromptsAPI {
46
46
  * @returns Instance of prompt api.
47
47
  */
48
48
  static async init(projectPath, appId, fs, options) {
49
- if (!fs) {
50
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
51
- }
49
+ fs = fs ?? (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
52
50
  await (0, i18n_1.initI18n)();
53
51
  const project = projectPath ? await (0, project_access_1.getProject)(projectPath) : undefined;
54
52
  return new PromptsAPI(fs, project, appId, options);
@@ -147,7 +145,8 @@ class PromptsAPI {
147
145
  const generator = map_1.PromptsGeneratorsMap.hasOwnProperty(config.type)
148
146
  ? map_1.PromptsGeneratorsMap[config.type]
149
147
  : undefined;
150
- return generator?.(this.context.appPath, config.answers, this.context.fs) ?? this.context.fs;
148
+ config.answers.buildingBlockData = { ...config.answers.buildingBlockData };
149
+ return generator?.(this.context.appPath, { ...config.answers }, this.context.fs) ?? this.context.fs;
151
150
  }
152
151
  /**
153
152
  * Method returns code snippet for passed answers and prompt type.
@@ -48,9 +48,10 @@ function getAdditionalDependencies(ui5Version) {
48
48
  * @param {CustomSection} data - a custom section configuration object
49
49
  * @param {string} manifestPath - path to the project's manifest.json
50
50
  * @param {Manifest} manifest - the application manifest
51
+ * @param {(baseId: string) => string} generateId - Function to generate unique IDs for the building block elements.
51
52
  * @returns enhanced configuration
52
53
  */
53
- function enhanceConfig(fs, data, manifestPath, manifest) {
54
+ function enhanceConfig(fs, data, manifestPath, manifest, generateId) {
54
55
  const config = { ...data };
55
56
  (0, defaults_1.setCommonDefaults)(config, manifestPath, manifest);
56
57
  // Apply event handler
@@ -65,7 +66,7 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
65
66
  config.content = config.control;
66
67
  }
67
68
  else {
68
- Object.assign(config, (0, defaults_1.getDefaultFragmentContentData)(config.name, config.eventHandler, undefined, undefined, false));
69
+ Object.assign(config, (0, defaults_1.getDefaultFragmentContentData)(config.name, generateId, config.eventHandler, undefined, undefined, false));
69
70
  }
70
71
  // Additional dependencies to include into 'Fragment.xml'
71
72
  config.dependencies = getAdditionalDependencies(config.minUI5Version);
@@ -82,13 +83,12 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
82
83
  */
83
84
  async function generate(basePath, customSection, manifestTemplateRoot, fs) {
84
85
  (0, validate_1.validateVersion)(customSection.minUI5Version);
85
- if (!fs) {
86
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
87
- }
86
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
88
87
  await (0, validate_1.validateBasePath)(basePath, fs);
88
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fs);
89
89
  const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
90
90
  // merge with defaults
91
- const completeSection = enhanceConfig(fs, customSection, manifestPath, manifest);
91
+ const completeSection = enhanceConfig(fs, customSection, manifestPath, manifest, fnGenerateId);
92
92
  // enhance manifest with section definition
93
93
  const filledTemplate = (0, ejs_1.render)(fs.read((0, node_path_1.join)(manifestTemplateRoot, `manifest.json`)), completeSection, {});
94
94
  (0, file_1.extendJSON)(fs, {
@@ -99,7 +99,7 @@ async function generate(basePath, customSection, manifestTemplateRoot, fs) {
99
99
  // add fragment
100
100
  const viewPath = (0, node_path_1.join)(completeSection.path, `${completeSection.fragmentFile ?? completeSection.name}.fragment.xml`);
101
101
  if (!fs.exists(viewPath)) {
102
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/FragmentWithVBox.xml'), viewPath, completeSection);
102
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/FragmentWithVBox.xml'), viewPath, completeSection, fnGenerateId);
103
103
  }
104
104
  return { editor: fs, section: completeSection };
105
105
  }
@@ -112,21 +112,20 @@ async function generate(basePath, customSection, manifestTemplateRoot, fs) {
112
112
  * @returns {Promise<Editor>} the updated mem-fs editor instance
113
113
  */
114
114
  async function generateCustomHeaderSection(basePath, customHeaderSection, fs) {
115
- if (!fs) {
116
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
117
- }
115
+ const fsEditor = fs ?? (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
116
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fsEditor); // initialize ID generator to ensure unique IDs across both section and edit fragment
118
117
  const manifestRoot = getManifestRoot('header-section', customHeaderSection.minUI5Version);
119
118
  const minVersion = (0, semver_1.coerce)(customHeaderSection.minUI5Version);
120
119
  let editSection;
121
120
  // Prepare 'templateEdit' - apply namespace and folder path resolution
122
121
  if (customHeaderSection.edit && (!minVersion || (0, semver_1.gte)(minVersion, '1.86.0'))) {
123
122
  editSection = customHeaderSection.edit;
124
- const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
123
+ const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fsEditor);
125
124
  // Set folder, ns and path for edit fragment
126
125
  (0, defaults_1.setCommonDefaults)(editSection, manifestPath, manifest);
127
126
  }
128
127
  // Call standard custom section generation
129
- const { editor, section } = await generate(basePath, customHeaderSection, manifestRoot, fs);
128
+ const { editor, section } = await generate(basePath, customHeaderSection, manifestRoot, fsEditor);
130
129
  // Handle 'templateEdit' - edit fragment details
131
130
  if (editSection) {
132
131
  // Apply event handler for edit fragment
@@ -142,12 +141,12 @@ async function generateCustomHeaderSection(basePath, customHeaderSection, fs) {
142
141
  editSection.content = editSection.control;
143
142
  }
144
143
  else {
145
- Object.assign(editSection, (0, defaults_1.getDefaultFragmentContentData)(editSection.name, editSection.eventHandler, false, true, false));
144
+ Object.assign(editSection, (0, defaults_1.getDefaultFragmentContentData)(editSection.name, fnGenerateId, editSection.eventHandler, false, true, false));
146
145
  }
147
146
  if (editSection.path) {
148
147
  const viewPath = (0, node_path_1.join)(editSection.path, `${editSection.name}.fragment.xml`);
149
148
  if (!editor.exists(viewPath)) {
150
- (0, file_1.copyTpl)(editor, (0, templates_1.getTemplatePath)('common/FragmentWithForm.xml'), viewPath, editSection);
149
+ (0, file_1.copyTpl)(editor, (0, templates_1.getTemplatePath)('common/FragmentWithForm.xml'), viewPath, editSection, fnGenerateId);
151
150
  }
152
151
  }
153
152
  }
@@ -49,9 +49,10 @@ function mergeViews(config, manifest) {
49
49
  * @param {CustomView} data - a custom view configuration object
50
50
  * @param {string} manifestPath - path to the project's manifest.json
51
51
  * @param {Manifest} manifest - the application manifest
52
+ * @param {IdGeneratorFunction} generateId - Function to generate unique IDs for the building block elements.
52
53
  * @returns enhanced configuration
53
54
  */
54
- function enhanceConfig(fs, data, manifestPath, manifest) {
55
+ function enhanceConfig(fs, data, manifestPath, manifest, generateId) {
55
56
  const config = { ...data };
56
57
  (0, defaults_1.setCommonDefaults)(config, manifestPath, manifest);
57
58
  // apply event handler
@@ -68,7 +69,7 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
68
69
  config.content = config.control;
69
70
  }
70
71
  else {
71
- config.content = (0, defaults_1.getDefaultFragmentContent)(config.name, config.eventHandler, true);
72
+ config.content = (0, defaults_1.getDefaultFragmentContent)(config.name, generateId, config.eventHandler, true);
72
73
  }
73
74
  return config;
74
75
  }
@@ -82,13 +83,12 @@ function enhanceConfig(fs, data, manifestPath, manifest) {
82
83
  */
83
84
  async function generateCustomView(basePath, customView, fs) {
84
85
  (0, validate_1.validateVersion)(customView.minUI5Version);
85
- if (!fs) {
86
- fs = (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
87
- }
86
+ fs ??= (0, mem_fs_editor_1.create)((0, mem_fs_1.create)());
88
87
  await (0, validate_1.validateBasePath)(basePath, fs);
88
+ const fnGenerateId = await (0, file_1.createIdGenerator)(basePath, fs);
89
89
  const { path: manifestPath, content: manifest } = await (0, utils_1.getManifest)(basePath, fs);
90
90
  // merge with defaults
91
- const completeView = enhanceConfig(fs, customView, manifestPath, manifest);
91
+ const completeView = enhanceConfig(fs, customView, manifestPath, manifest, fnGenerateId);
92
92
  // enhance manifest with view definition
93
93
  const filledTemplate = (0, ejs_1.render)(fs.read((0, templates_1.getTemplatePath)('view/manifest.json')), completeView, {});
94
94
  (0, file_1.extendJSON)(fs, {
@@ -100,10 +100,10 @@ async function generateCustomView(basePath, customView, fs) {
100
100
  if (customView.viewUpdate !== false) {
101
101
  const viewPath = (0, node_path_1.join)(completeView.path, `${completeView.name}.fragment.xml`);
102
102
  if (completeView.control === true) {
103
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('view/ext/CustomViewWithTable.xml'), viewPath, completeView);
103
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('view/ext/CustomViewWithTable.xml'), viewPath, completeView, fnGenerateId);
104
104
  }
105
105
  else if (!fs.exists(viewPath)) {
106
- (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeView);
106
+ (0, file_1.copyTpl)(fs, (0, templates_1.getTemplatePath)('common/Fragment.xml'), viewPath, completeView, fnGenerateId);
107
107
  }
108
108
  }
109
109
  return fs;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sap-ux/fe-fpm-writer",
3
3
  "description": "SAP Fiori elements flexible programming model writer",
4
- "version": "0.42.19",
4
+ "version": "0.42.20",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/SAP/open-ux-tools.git",
@@ -42,7 +42,7 @@
42
42
  "@types/semver": "7.7.1",
43
43
  "@types/vinyl": "2.0.7",
44
44
  "@sap-ux/i18n": "0.3.9",
45
- "@sap-ux/ui-prompting": "0.6.7"
45
+ "@sap-ux/ui-prompting": "0.6.8"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=20.x"
@@ -1,6 +1,7 @@
1
1
  <<%- config.aggregationNamespace %>:buttonGroups>
2
- <% (data.buttonGroups || []).forEach(function(buttonGroup) { %>
2
+ <% (data.buttonGroups || []).forEach(function(buttonGroup, idKey) { %>
3
3
  <richtexteditor:ButtonGroup
4
+ id="<%- buttonGroup.id || ids[idKey] %>"
4
5
  name="<%- buttonGroup.name %>"
5
6
  visible="<%- buttonGroup.visible !== undefined ? buttonGroup.visible : true %>"
6
7
  buttons="<%- buttonGroup.buttons %>"<%
@@ -12,9 +13,6 @@
12
13
  } %><%
13
14
  if (buttonGroup.row !== undefined) { %>
14
15
  row="<%- buttonGroup.row %>"<%
15
- } %><%
16
- if (buttonGroup.id !== undefined) { %>
17
- id="<%- buttonGroup.id %>"<%
18
16
  } %>/>
19
17
  <% }); %>
20
18
  </<%- config.aggregationNamespace %>:buttonGroups>
@@ -1,5 +1,5 @@
1
1
  <core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:f="sap.ui.layout.form"<%- typeof dependencies !== 'undefined' ? ` ${dependencies}` : '' %>>
2
- <f:FormElement<%- typeof requireAttribute !== 'undefined' ? ` ${requireAttribute}` : '' %>>
2
+ <f:FormElement id="<%- ids.formElement %>"<%- typeof requireAttribute !== 'undefined' ? ` ${requireAttribute}` : '' %>>
3
3
  <f:fields>
4
4
  <%- content %>
5
5
  </f:fields>
@@ -1,5 +1,5 @@
1
1
  <core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m"<%- typeof dependencies !== 'undefined' ? ` ${dependencies}` : '' %>>
2
- <VBox<%- typeof requireAttribute !== 'undefined' ? ` ${requireAttribute}` : '' %>>
2
+ <VBox id="<%- ids.vbox %>"<%- typeof requireAttribute !== 'undefined' ? ` ${requireAttribute}` : '' %>>
3
3
  <%- content %>
4
4
  </VBox>
5
5
  </core:FragmentDefinition>
@@ -1,15 +1,15 @@
1
1
  <core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m"><%if (typeof eventHandler !== 'undefined') {%>
2
2
  <ComboBox
3
- <% if (typeof controlID !== 'undefined') { %>id="<%- controlID %>"<% } %>
3
+ id="<% if (typeof controlID !== 'undefined' && controlID) { %><%- controlID %><% } else { %><%- ids.comboBox %><% } %>"
4
4
  width="100%"
5
5
  core:require="{handler: '<%- eventHandler.split('.').slice(0, -1).join('/') %>'}"
6
6
  selectedKey="{path: 'filterValues>', type: 'sap.fe.macros.filter.type.Value', formatOptions: { operator: '<%- eventHandler %>' }}"
7
7
  ><% } else { %>
8
- <ComboBox<% if (typeof controlID !== 'undefined') { %> id="<%- controlID %>"<% } %> width="100%"><% } %>
8
+ <ComboBox id="<% if (typeof controlID !== 'undefined' && controlID) { %><%- controlID %><% } else { %><%- ids.comboBox %><% } %>" width="100%"><% } %>
9
9
  <items>
10
- <core:Item key="0" text="Item1"/>
11
- <core:Item key="1" text="Item2"/>
12
- <core:Item key="2" text="Item3"/>
10
+ <core:Item id="<%- ids.item1 %>" key="0" text="Item1"/>
11
+ <core:Item id="<%- ids.item2 %>" key="1" text="Item2"/>
12
+ <core:Item id="<%- ids.item3 %>" key="2" text="Item3"/>
13
13
  </items>
14
14
  </ComboBox>
15
15
  </core:FragmentDefinition>
@@ -1,6 +1,6 @@
1
1
  <mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"
2
2
  xmlns:html="http://www.w3.org/1999/xhtml" controllerName="<%- ns %>.<%- name %>">
3
- <Page id="<%- name %>" title="{i18n><%- name %>Title}">
3
+ <Page id="<%- ids.page %>" title="{i18n><%- name %>Title}">
4
4
  <content></content>
5
5
  </Page>
6
6
  </mvc:View>
@@ -1,6 +1,6 @@
1
1
  <mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:macros="sap.fe.macros"
2
2
  xmlns:html="http://www.w3.org/1999/xhtml" controllerName="<%- ns %>.<%- name %>">
3
- <Page id="<%- name %>" title="{i18n><%- name %>Title}">
3
+ <Page id="<%- ids.page %>" title="{i18n><%- name %>Title}">
4
4
  <content></content>
5
5
  </Page>
6
6
  </mvc:View>
@@ -1,3 +1,3 @@
1
1
  <core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:macros="sap.fe.macros" xmlns:u="sap.ui.unified">
2
- <macros:Table id="<%- name %>" metaPath="@com.sap.vocabularies.UI.v1.LineItem" displayMode="true" />
2
+ <macros:Table id="<%- ids.table %>" metaPath="@com.sap.vocabularies.UI.v1.LineItem" displayMode="true" />
3
3
  </core:FragmentDefinition>