@ui5/builder 5.0.0-alpha.3 → 5.0.0-alpha.5

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 CHANGED
@@ -4,6 +4,60 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
4
4
 
5
5
  A list of unreleased changes can be found [here](https://github.com/SAP/ui5-builder/compare/v4.0.11...HEAD).
6
6
 
7
+ ## [5.0.0-alpha.5](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.4...builder-v5.0.0-alpha.5) (2026-06-24)
8
+
9
+
10
+ ### Features
11
+
12
+ * **builder:** Adapt build tasks for incremental builds ([ace5ebb](https://github.com/UI5/cli/commit/ace5ebb6bf10b7d72db6397460f12e9040121456))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **builder:** Also detect import.meta as ESM indicator ([5d1dcb7](https://github.com/UI5/cli/commit/5d1dcb719056eba85dc13271493baa8d5cbd1bd1))
18
+ * **builder:** Reduce noise from ESM parse errors ([4e7f69a](https://github.com/UI5/cli/commit/4e7f69aede38571dcd4643414ff54ab6131a5c73))
19
+ * **builder:** Skip ESM modules during bundling and log errors ([20bcdf2](https://github.com/UI5/cli/commit/20bcdf212c30569a6aba5c7c61d5717bfd9b1d0b))
20
+ * **builder:** Update lbt/bundle/Resolver to ensure deterministic ordering of raw modules ([9d19923](https://github.com/UI5/cli/commit/9d19923437f41f511cb51fb8477e7a2dec1f0d13))
21
+
22
+
23
+ ### Dependencies
24
+
25
+ * Bump espree from 10.4.0 to 11.2.0 ([#1381](https://github.com/UI5/cli/issues/1381)) ([29a00b2](https://github.com/UI5/cli/commit/29a00b236e4c410e4a47ccb0842e851ff9e2ab4a))
26
+ * Update npm dependencies ([0ac8ebb](https://github.com/UI5/cli/commit/0ac8ebbb5ffe9fc6e4455244cf5da50e4c540c5c))
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @ui5/fs bumped from ^5.0.0-alpha.4 to ^5.0.0-alpha.5
30
+ * @ui5/logger bumped from ^5.0.0-alpha.4 to ^5.0.0-alpha.5
31
+ * devDependencies
32
+ * @ui5/project bumped from ^5.0.0-alpha.4 to ^5.0.0-alpha.5
33
+
34
+ ## [5.0.0-alpha.4](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.3...builder-v5.0.0-alpha.4) (2026-03-24)
35
+
36
+
37
+ ### Features
38
+
39
+ * **builder:** Add dynamic section for API reference during JSDoc build ([47970f7](https://github.com/UI5/cli/commit/47970f74c4adf63625e4f330f19090083f563f82))
40
+ * **builder:** Add hierarchical subsection support for API reference sections ([49a3ef5](https://github.com/UI5/cli/commit/49a3ef569074ff84b5f6dfc2cfc619cd1654c2c8))
41
+ * **builder:** Automatically add [@lends](https://github.com/lends) tag to extend calls during JSdoc build ([3547f42](https://github.com/UI5/cli/commit/3547f425b33663f7e5e2618a6d876fe018058d99))
42
+ * **builder:** In JSDoc build, push the deprecation of namespaces down to the contained APIs ([71c08f2](https://github.com/UI5/cli/commit/71c08f2bb1d2cfee34ab9ca2e9e6042b5e67ae0c))
43
+ * **builder:** Mark cross-references to typedefs in the api.json (SDK variant) ([a474700](https://github.com/UI5/cli/commit/a47470050e804192bfab857f6c224faa6e445e10))
44
+
45
+
46
+ ### Bug Fixes
47
+
48
+ * **builder:** Improve JSDoc support for "module:" syntax ([aca05f0](https://github.com/UI5/cli/commit/aca05f0b75561f33842ccb30248ede3a82f2ae46))
49
+ * **builder:** JSDoc: avoid errors when symbol has no events ([6e517e6](https://github.com/UI5/cli/commit/6e517e6c7f480784b93e37e94dcf74e0a689427f))
50
+
51
+
52
+ ### Dependencies
53
+
54
+ * The following workspace dependencies were updated
55
+ * dependencies
56
+ * @ui5/fs bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
57
+ * @ui5/logger bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
58
+ * devDependencies
59
+ * @ui5/project bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
60
+
7
61
  ## [5.0.0-alpha.3](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.2...builder-v5.0.0-alpha.3) (2026-02-13)
8
62
 
9
63
 
@@ -597,10 +597,11 @@ class BundleBuilder {
597
597
  }
598
598
  }
599
599
 
600
- writeRequires(section) {
600
+ async writeRequires(section) {
601
601
  if (section.modules.length === 0) {
602
602
  return;
603
603
  }
604
+
604
605
  this.outW.ensureNewLine();
605
606
  if (section.async === false) {
606
607
  section.modules.forEach( (module) => {
@@ -767,7 +768,7 @@ async function rewriteDefine({moduleName, moduleContent, moduleSourceMap}) {
767
768
  } catch (e) {
768
769
  log.error(`Error while parsing ${moduleName}: ${e.message}`);
769
770
  log.verbose(e.stack);
770
- return {};
771
+ return null;
771
772
  }
772
773
 
773
774
  if ( ast.type === Syntax.Program &&
@@ -67,6 +67,7 @@ class ResolvedBundleDefinition {
67
67
  return pool.getModuleInfo(submodule).then(
68
68
  (subinfo) => {
69
69
  if (!bundleInfo.subModules.includes(subinfo.name) &&
70
+ subinfo.format !== ModuleInfo.Format.ESM &&
70
71
  (!subinfo.requiresTopLevelScope ||
71
72
  (subinfo.requiresTopLevelScope && allowStringBundling))) {
72
73
  bundleInfo.addSubModule(subinfo);
@@ -4,6 +4,7 @@
4
4
 
5
5
  import topologicalSort from "../graph/topologicalSort.js";
6
6
  import {getRendererName} from "../UI5ClientConstants.js";
7
+ import ModuleInfo from "../resources/ModuleInfo.js";
7
8
  import ResourceFilterList from "../resources/ResourceFilterList.js";
8
9
  import {SectionType} from "./BundleDefinition.js";
9
10
  import ResolvedBundleDefinition from "./ResolvedBundleDefinition.js";
@@ -135,6 +136,16 @@ class BundleResolver {
135
136
  const dependencyInfo = resource && resource.info;
136
137
  let promises = [];
137
138
 
139
+ // Skip ESM modules — they can't be bundled
140
+ if (resource?.info?.format === ModuleInfo.Format.ESM) {
141
+ log.error(
142
+ `Module ${resourceName} is an ECMAScript Module (ESM), ` +
143
+ `which is not supported for bundling. ` +
144
+ `The module will be skipped.`
145
+ );
146
+ return;
147
+ }
148
+
138
149
  if ( isBundle && !decomposable ) {
139
150
  resource.info.subModules.forEach(
140
151
  (included) => {
@@ -318,6 +329,15 @@ class BundleResolver {
318
329
  return collectModulesForSection(section).
319
330
  then( (modules) => {
320
331
  if ( section.mode == SectionType.Raw && section.sort !== false ) {
332
+ // Sort modules by their filter definition order to ensure
333
+ // deterministic input for the topological sort.
334
+ // Topological sort preserves input order for tie-breaking,
335
+ // so this ensures modules at the same dependency level
336
+ // appear in the order defined by the section filters.
337
+ const filterOrder = new ResourceFilterList(section.filters, fileTypes);
338
+ modules.sort((a, b) => {
339
+ return filterOrder.matchIndex(a) - filterOrder.matchIndex(b);
340
+ });
321
341
  // sort the modules in topological order
322
342
  return topologicalSort(pool, modules).then( (modules) => {
323
343
  log.silly(` Resolved modules (sorted): ${modules}`);
@@ -39,7 +39,7 @@ class SapUiDefineCall {
39
39
  this.dependencies = this.dependencyArray.elements.map( (elem) => {
40
40
  const value = getStringValue(elem);
41
41
  if ( !value ) {
42
- throw new TypeError();
42
+ throw new TypeError(`dependency element is not a string literal in module ${this.name}`);
43
43
  }
44
44
  return resolveRelativeRequireJSName(this.name, value);
45
45
  });
@@ -25,7 +25,8 @@ const CONDITIONAL = 2;
25
25
  const Format = {
26
26
  UI5_LEGACY: "ui5-declare",
27
27
  UI5_DEFINE: "ui5-define",
28
- AMD: "amd"
28
+ AMD: "amd",
29
+ ESM: "esm"
29
30
  };
30
31
 
31
32
  /**
@@ -124,6 +124,16 @@ export default class ResourceFilterList {
124
124
  );
125
125
  }
126
126
 
127
+ matchIndex(candidate) {
128
+ for (let i = 0; i < this.matchers.length; i++) {
129
+ const matcher = this.matchers[i];
130
+ if (matcher.include && matcher.calcMatch(candidate, false)) {
131
+ return i;
132
+ }
133
+ }
134
+ return this.matchers.length;
135
+ }
136
+
127
137
  toString() {
128
138
  return this.matchers.map((matcher) => matcher.pattern).join(",");
129
139
  }
@@ -62,8 +62,12 @@ async function determineDependencyInfo(resource, rawInfo, pool) {
62
62
  try {
63
63
  ast = parseJS(code, {comment: true});
64
64
  } catch (err) {
65
- log.error(`Failed to parse ${resource.name}: ${err.message}`);
65
+ log.verbose(`Failed to parse ${resource.name}: ${err.message}`);
66
66
  log.verbose(err.stack);
67
+ if (err.message.includes("'import' and 'export' may appear only with 'sourceType: module'") ||
68
+ err.message.includes("Cannot use 'import.meta' outside a module")) {
69
+ info.format = ModuleInfo.Format.ESM;
70
+ }
67
71
  }
68
72
  if (ast) {
69
73
  try {
@@ -243,4 +247,3 @@ class ResourcePool {
243
247
  }
244
248
 
245
249
  export default ResourcePool;
246
-
@@ -18,8 +18,8 @@ import {createResource} from "@ui5/fs/resourceFactory";
18
18
  * @param {@ui5/fs/Resource[]} parameters.resources List of resources to be processed
19
19
  * @param {object} parameters.options Options
20
20
  * @param {string} parameters.options.pathPrefix Prefix for bundle path
21
- * @param {string} parameters.options.hasFlexBundleVersion true if minUI5Version >= 1.73 than
22
- * create flexibility-bundle.json
21
+ * @param {string} parameters.options.hasFlexBundleVersion true if minUI5Version >= 1.73
22
+ * and create flexibility-bundle.json
23
23
  * @param {object} [parameters.existingFlexBundle={}] Object with existing flexibility-bundle.json
24
24
  * to merge with new changes
25
25
  * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with flex changes bundle resources
@@ -45,6 +45,7 @@ export default function({resources, options: {pathPrefix, hasFlexBundleVersion},
45
45
  const variants = [];
46
46
  const variantChanges = [];
47
47
  const variantManagementChanges = [];
48
+ const annotationChanges = [];
48
49
 
49
50
  changesContent.forEach(function(content) {
50
51
  if (content.layer === "VENDOR") {
@@ -74,14 +75,23 @@ export default function({resources, options: {pathPrefix, hasFlexBundleVersion},
74
75
  case "ctrl_variant_management_change":
75
76
  variantManagementChanges.push(content);
76
77
  break;
78
+ case "annotation_change":
79
+ annotationChanges.push(content);
80
+ break;
77
81
  }
78
82
  });
79
83
 
80
- if (!hasFlexBundleVersion && (compVariants.length != 0 || variants.length != 0 || variantChanges.length != 0 ||
81
- variantDependentControlChanges.length != 0 || variantManagementChanges.length != 0)) {
84
+ if (!hasFlexBundleVersion && (
85
+ compVariants.length != 0 ||
86
+ variants.length != 0 ||
87
+ variantChanges.length != 0 ||
88
+ variantDependentControlChanges.length != 0 ||
89
+ variantManagementChanges.length != 0 ||
90
+ annotationChanges.length != 0
91
+ )) {
82
92
  throw new Error(
83
- "There are some control variant changes in the changes folder. This only works with a " +
84
- "minUI5Version 1.73.0. Please update the minUI5Version in the manifest.json to 1.73.0 or higher");
93
+ "There are some files in the changes folder supported only with a UI5 version 1.73 and above. " +
94
+ "Please update the minUI5Version in the manifest.json to 1.73 or higher");
85
95
  }
86
96
  // create changes-bundle.json
87
97
  if (!hasFlexBundleVersion) {
@@ -89,6 +99,7 @@ export default function({resources, options: {pathPrefix, hasFlexBundleVersion},
89
99
  } else {
90
100
  bundleName = "flexibility-bundle.json";
91
101
  let newChangeFormat = {
102
+ annotationChanges,
92
103
  changes,
93
104
  compVariants,
94
105
  variants,
@@ -58,7 +58,7 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
58
58
  * Excludes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later
59
59
  * exclusion overrides an earlier inclusion, and vice versa.
60
60
  *
61
- * <caption>List of modules as glob patterns that should be in- or excluded</caption>
61
+ * <i>Example:</i>
62
62
  *
63
63
  * ```js
64
64
  * // Includes everything from "some/path/to/module/",
@@ -163,10 +163,11 @@ async function buildJsdoc({sourcePath, configPath}) {
163
163
  "--verbose",
164
164
  sourcePath
165
165
  ];
166
- const exitCode = await new Promise((resolve /* , reject */) => {
166
+ const exitCode = await new Promise((resolve, reject) => {
167
167
  const child = spawn("node", args, {
168
168
  stdio: ["ignore", "ignore", "inherit"]
169
169
  });
170
+ child.on("error", reject);
170
171
  child.on("close", resolve);
171
172
  });
172
173
 
@@ -47,10 +47,10 @@ const typeParser = new TypeParser();
47
47
  * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information
48
48
  * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or
49
49
  * an array of paths of those files
50
- * @param {string} sFAQDir Path to the directory containing the sources for the FAQ section in APiRef
50
+ * @param {string} sSectionsDir Path to the directory containing the sources for the sections (FAQ, documentation, etc.) in the API Reference
51
51
  * @returns {Promise} A Promise that resolves after the transformation has been completed
52
52
  */
53
- function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sFAQDir, options) {
53
+ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sSectionsDir, options) {
54
54
  const fs = options && options.fs || require("fs");
55
55
  const returnOutputFiles = options && !!options.returnOutputFiles;
56
56
 
@@ -59,7 +59,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
59
59
  log.info(" output file: " + sOutputFile);
60
60
  log.info(" library file: " + sLibraryFile);
61
61
  log.info(" dependency dir: " + vDependencyAPIFiles);
62
- log.info(" FAQ src dir: " + sFAQDir);
62
+ log.info(" sections src dir: " + sSectionsDir);
63
63
  if (options && options.fs) {
64
64
  log.info("Using custom fs.");
65
65
  }
@@ -156,9 +156,10 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
156
156
  * UI5Types: ["sap.ui.core.Control"] // skip template if its value is "${0}" (default value)
157
157
  * }
158
158
  * @param {string} sComplexType
159
+ * @param {Object<string,object>} oAllDependentAPIs Map of entity names to their symbol
159
160
  * @returns {{template=: string, UI5Types=: string[]}}
160
161
  */
161
- function parseUI5Types(sComplexType) {
162
+ function parseUI5Types(sComplexType, oAllDependentAPIs) {
162
163
  let oParsed;
163
164
  try {
164
165
  oParsed = typeParser.parseSimpleTypes(sComplexType, isUI5Type);
@@ -176,6 +177,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
176
177
 
177
178
  if (oParsed.simpleTypes?.length) { // it can be empty if none of the included simple types satisfied the filter function
178
179
  result.UI5Types = oParsed.simpleTypes;
180
+
181
+ // check whether the type refers to a typedef and set a marker
182
+ if (result.template == null && result.UI5Types.length === 1) {
183
+ const symbol = oAllDependentAPIs[result.UI5Types[0]];
184
+ if (symbol?.kind === "typedef" && Array.isArray(symbol.properties)) {
185
+ result.refersToTypedef = true;
186
+ }
187
+ }
179
188
  }
180
189
 
181
190
  return result;
@@ -266,6 +275,8 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
266
275
  * @param {object} oChainObject chain object
267
276
  */
268
277
  const transformApiJson = function (oChainObject) {
278
+ const {oAllDependentAPIs} = oChainObject;
279
+
269
280
  // Function is a copy from: LibraryInfo.js => LibraryInfo.prototype._getActualComponent => "match" inline method
270
281
  function matchComponent(sModuleName, sPattern) {
271
282
  sModuleName = sModuleName.toLowerCase();
@@ -435,7 +446,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
435
446
  // Types
436
447
  oParameter.types = [];
437
448
  if (oParameter.type) {
438
- oParameter.typeInfo = parseUI5Types(oParameter.type);
449
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
439
450
  // Keep file size in check
440
451
  delete oParameter.type;
441
452
  }
@@ -520,7 +531,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
520
531
  // Type
521
532
  if (oSymbol.kind !== "enum") { // enum properties don't have an own type
522
533
  if (oProperty.type) {
523
- oProperty.typeInfo = parseUI5Types(oProperty.type);
534
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
524
535
  // Keep file size in check
525
536
  delete oProperty.type;
526
537
  }
@@ -560,7 +571,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
560
571
 
561
572
  // Type
562
573
  if (oProperty.type) {
563
- oProperty.typeInfo = parseUI5Types(oProperty.type);
574
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
564
575
  // Keep file size in check
565
576
  delete oProperty.type;
566
577
  }
@@ -657,7 +668,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
657
668
  Object.keys(aParams).forEach((sParam) => {
658
669
  const sSince = aParams[sParam].since;
659
670
  const oDeprecated = aParams[sParam].deprecated;
660
- const oEvtInSymbol = oSymbol.events.find((e) => e.name === oEvent.name);
671
+ const oEvtInSymbol = oSymbol.events?.find((e) => e.name === oEvent.name);
661
672
  const oParamInSymbol = oEvtInSymbol && oEvtInSymbol.parameters[0] &&
662
673
  oEvtInSymbol.parameters[0].parameterProperties &&
663
674
  oEvtInSymbol.parameters[0].parameterProperties.getParameters &&
@@ -687,7 +698,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
687
698
 
688
699
  // Link Enabled
689
700
  if (oSetting.type) {
690
- oSetting.typeInfo = parseUI5Types(oSetting.type);
701
+ oSetting.typeInfo = parseUI5Types(oSetting.type, oAllDependentAPIs);
691
702
  delete oSetting.type; // Keep file size in check
692
703
  }
693
704
 
@@ -756,7 +767,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
756
767
  oEvent.parameters.forEach((oParameter) => {
757
768
 
758
769
  if (oParameter.type) {
759
- oParameter.typeInfo = parseUI5Types(oParameter.type);
770
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
760
771
  delete oParameter.type; // Keep file size in check
761
772
  }
762
773
 
@@ -789,7 +800,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
789
800
  if (oSymbol.methods) {
790
801
 
791
802
  // Pre-process methods
792
- methods.buildMethodsModel(oSymbol.methods);
803
+ methods.buildMethodsModel(oSymbol.methods, oAllDependentAPIs);
793
804
 
794
805
  oSymbol.methods.forEach((oMethod) => {
795
806
 
@@ -950,6 +961,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
950
961
  }
951
962
 
952
963
  function loadDependencyLibraryFiles (oChainObject) {
964
+ const oAllDependentAPIs = oChainObject.oAllDependentAPIs = Object.create(null);
965
+ // add symbols from current library
966
+ if (oChainObject.fileData.symbols) {
967
+ for (const oSymbol of oChainObject.fileData.symbols) {
968
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
969
+ }
970
+ }
971
+
953
972
  if (!oChainObject.aDependentLibraryFiles) {
954
973
  return oChainObject;
955
974
  }
@@ -978,6 +997,12 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
978
997
  // In this case we don't add it to the dependency list to skip double iteration.
979
998
  if (oData && oChainObject.fileData.library !== oData.library) {
980
999
  oDependentAPIs[oData.library] = oData.symbols;
1000
+
1001
+ if (Array.isArray(oData.symbols)) {
1002
+ for (const oSymbol of oData.symbols) {
1003
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
1004
+ }
1005
+ }
981
1006
  }
982
1007
  });
983
1008
 
@@ -987,22 +1012,69 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
987
1012
  }
988
1013
 
989
1014
  /**
990
- * Check for existence of FAQ data
991
- * (FAQ data must be defined as *.md files in the <code>sFAQDir</code>)
1015
+ * Check for existence of sections data
1016
+ * (Sections data must be defined as *.md files in the <code>sSectionsDir</code>)
992
1017
  * and add a boolean flag in case it exists
993
1018
  *
994
1019
  * @param oChainObject chain object
995
1020
  */
996
- function addFlagsForFAQData(oChainObject) {
997
- if (!sFAQDir) {
1021
+ function addFlagsForSectionsData(oChainObject) {
1022
+ if (!sSectionsDir) {
998
1023
  return oChainObject;
999
1024
  }
1025
+
1000
1026
  const slibName = oChainObject.fileData.library;
1027
+
1001
1028
  oChainObject.fileData.symbols.forEach(function(symbol) {
1002
- const sfile = symbol.name.replace(slibName, "").replace(/[.]/g, "/") + ".md";
1003
- if (fs.existsSync(path.join(sFAQDir, sfile))) {
1004
- symbol.hasFAQ = true;
1029
+ const sComponentPath = symbol.name.replace(slibName, "").replace(/^[.]/, "").replace(/[.]/g, "/");
1030
+ if (!sComponentPath) {
1031
+ return;
1005
1032
  }
1033
+
1034
+ const componentDir = path.join(sSectionsDir, sComponentPath);
1035
+ if (fs.existsSync(componentDir)) {
1036
+ try {
1037
+ const dirContents = fs.readdirSync(componentDir, { withFileTypes: true });
1038
+
1039
+ // Separate files and directories in one pass
1040
+ const mdFiles = [];
1041
+ const dirNames = new Set();
1042
+ dirContents.forEach((dirent) => {
1043
+ if (dirent.isFile() && dirent.name.endsWith('.md')) {
1044
+ mdFiles.push(dirent.name.replace(/\.md$/, ''));
1045
+ } else if (dirent.isDirectory()) {
1046
+ dirNames.add(dirent.name);
1047
+ }
1048
+ });
1049
+
1050
+ // Build customSections array with subsection support
1051
+ const customSections = mdFiles.map(function(sectionName) {
1052
+ // Check if there's a matching directory for this section
1053
+ if (dirNames.has(sectionName)) {
1054
+ try {
1055
+ const subsectionFiles = fs.readdirSync(path.join(componentDir, sectionName), { withFileTypes: true })
1056
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
1057
+ .map((dirent) => dirent.name.replace(/\.md$/, ''));
1058
+
1059
+ if (subsectionFiles.length > 0) {
1060
+ return { name: sectionName, hasSubsections: true, subsections: subsectionFiles };
1061
+ }
1062
+ } catch (error) {
1063
+ log.error('Error scanning subsection directory:', path.join(componentDir, sectionName), error);
1064
+ }
1065
+ }
1066
+ return sectionName;
1067
+ });
1068
+
1069
+ if (customSections.length > 0) {
1070
+ symbol.customSections = customSections;
1071
+ }
1072
+
1073
+ } catch (error) {
1074
+ log.error('Error scanning component sections directory:', componentDir, error);
1075
+ }
1076
+ }
1077
+
1006
1078
  });
1007
1079
  return oChainObject;
1008
1080
  }
@@ -2175,7 +2247,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2175
2247
  * Adjusts methods info so that it can be easily displayed in a table
2176
2248
  * @param aMethods - the methods array initially coming from the server
2177
2249
  */
2178
- buildMethodsModel: function (aMethods) {
2250
+ buildMethodsModel: function (aMethods, oAllDependentAPIs) {
2179
2251
  var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) {
2180
2252
  if (oParameter.parameterProperties) {
2181
2253
  Object.keys(oParameter.parameterProperties).forEach(function (sProperty) {
@@ -2185,7 +2257,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2185
2257
 
2186
2258
  // Handle types
2187
2259
  if (oProperty.type) {
2188
- oProperty.typeInfo = parseUI5Types(oProperty.type);
2260
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
2189
2261
  // Keep file size in check
2190
2262
  delete oProperty.type;
2191
2263
  }
@@ -2217,7 +2289,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2217
2289
  if (oMethod.parameters) {
2218
2290
  oMethod.parameters.forEach(function (oParameter) {
2219
2291
  if (oParameter.type) {
2220
- oParameter.typeInfo = parseUI5Types(oParameter.type);
2292
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
2221
2293
  // Keep file size in check
2222
2294
  delete oParameter.type;
2223
2295
  }
@@ -2238,7 +2310,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2238
2310
  // Handle return values
2239
2311
  if (oMethod.returnValue && oMethod.returnValue.type) {
2240
2312
  // Handle types
2241
- oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type);
2313
+ oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type, oAllDependentAPIs);
2242
2314
  }
2243
2315
 
2244
2316
  });
@@ -2375,6 +2447,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2375
2447
  inputFile: sInputFile,
2376
2448
  outputFile: sOutputFile,
2377
2449
  libraryFile: sLibraryFile,
2450
+ sectionsDir: sSectionsDir,
2378
2451
  aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null
2379
2452
  };
2380
2453
 
@@ -2387,7 +2460,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2387
2460
  .then(getAPIJSONPromise)
2388
2461
  .then(loadDependencyLibraryFiles)
2389
2462
  .then(transformApiJson)
2390
- .then(addFlagsForFAQData)
2463
+ .then(addFlagsForSectionsData)
2391
2464
  .then(createApiRefApiJson);
2392
2465
  return p;
2393
2466
 
@@ -73,7 +73,8 @@ const path = require('jsdoc/path');
73
73
  const logger = require('jsdoc/util/logger');
74
74
  const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || env.opts.sapui5 || {};
75
75
  const escope = require("escope");
76
- const {isSemVer} = require("./template/utils/versionUtil");
76
+ const templatePath = path.getResourcePath(env.opts.template) || "./template";
77
+ const {isSemVer} = require(`${templatePath}/utils/versionUtil`);
77
78
 
78
79
  /* ---- logging ---- */
79
80
 
@@ -1220,11 +1221,13 @@ function collectClassInfo(extendCall, classDoclet) {
1220
1221
  if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) {
1221
1222
  baseType = classDoclet.augments[0];
1222
1223
  }
1224
+ const jsdocClassName = classDoclet?.kind === "class" ? classDoclet.alias || classDoclet.longname : undefined;
1225
+
1223
1226
  if ( isMemberExpression(extendCall.callee) ) {
1224
1227
  const baseCandidate = getResolvedObjectName(extendCall.callee.object);
1225
1228
  if ( baseCandidate && baseType == null ) {
1226
1229
  baseType = baseCandidate;
1227
- } else if ( baseCandidate !== baseType ) {
1230
+ } else if ( baseCandidate !== toRuntimeMetadataName(baseType) ) {
1228
1231
  future(`documented base type '${baseType}' doesn't match technical base type '${baseCandidate}'`);
1229
1232
  }
1230
1233
  }
@@ -1283,6 +1286,32 @@ function collectClassInfo(extendCall, classDoclet) {
1283
1286
  }
1284
1287
 
1285
1288
  const classInfoNode = extendCall.arguments[1];
1289
+ if (classInfoNode?.type === Syntax.ObjectExpression && jsdocClassName && addLeadingCommentNode) {
1290
+ // Take care of the @lends comment only
1291
+ // - when the class info is an object literal (otherwise lends makes no sense)
1292
+ // - when a class name is found (it might differ from the runtime metadata name)
1293
+ // - when the entity is documented as a class (or else '.prototype' would be wrong)
1294
+ const classInfoComment = getLeadingCommentNode(classInfoNode);
1295
+ if (classInfoComment == null) {
1296
+ // If the class info object doesn't have a @lends tag, add it
1297
+ const raw = `/** @lends ${jsdocClassName}.prototype */`;
1298
+ info(`lends comment injected for class '${jsdocClassName}'`);
1299
+ addLeadingCommentNode(classInfoNode, {
1300
+ type: "CommentBlock",
1301
+ raw,
1302
+ value: raw.slice(2,-2),
1303
+ loc: classInfoNode.loc
1304
+ });
1305
+ } else {
1306
+ // Otherwise check whether the name matches the name of the described class
1307
+ const doclet = new Doclet(getRawComment(classInfoComment), {});
1308
+ // The name after the @lends is surprisingly stored in the 'alias' property
1309
+ if (doclet.alias !== `${jsdocClassName}.prototype`) {
1310
+ future(`lends comment '${doclet.alias}' doesn't match documented class name '${jsdocClassName}'`);
1311
+ }
1312
+ }
1313
+ }
1314
+
1286
1315
  const classInfoMap = createPropertyMap(classInfoNode);
1287
1316
  if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) {
1288
1317
  warning(`class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '${classInfoMap.metadata.value.type}'.`);
@@ -1772,7 +1801,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1772
1801
  }
1773
1802
 
1774
1803
  function name(prefix, n, _static) {
1775
- return oClassInfo.name + rname(prefix, n, _static);
1804
+ return jsdocClassName + rname(prefix, n, _static);
1776
1805
  }
1777
1806
 
1778
1807
  /*
@@ -1884,6 +1913,18 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1884
1913
  const p = m ? m.index : -1;
1885
1914
  const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0;
1886
1915
  const classAccess = doclet?.access ?? "public";
1916
+
1917
+ // If doclet exists and has an alias or longname, use that when building the fully qualified
1918
+ // names of methods or events.
1919
+ // Do this only when the doclet is of kind "class". Many TypeScript classes unfortunately
1920
+ // use a broken pattern with @namespace (needs to be deprecated in babel-plugin-transform-modules-ui5)
1921
+ const jsdocClassName =
1922
+ (doclet?.kind === "class" ? doclet.alias || doclet.longname : undefined) || oClassInfo.name;
1923
+ if (jsdocClassName !== oClassInfo.name) {
1924
+ info("jsdoc class name differs from runtime metadata name", jsdocClassName, oClassInfo.name);
1925
+ }
1926
+
1927
+ const displayClassName = oClassInfo.name;
1887
1928
  const visibility = createVisibilityTags(classAccess, doclet?.__ui5?.stakeholders);
1888
1929
  const thisClass = 'this'; // oClassInfo.name
1889
1930
 
@@ -2007,7 +2048,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2007
2048
  }
2008
2049
 
2009
2050
  newJSDoc([
2010
- "Returns a metadata object for class " + oClassInfo.name + ".",
2051
+ "Returns a metadata object for class " + displayClassName + ".",
2011
2052
  "",
2012
2053
  "@returns {sap.ui.base.Metadata} Metadata object describing this class",
2013
2054
  visibility,
@@ -2018,7 +2059,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2018
2059
 
2019
2060
  if ( !oClassInfo["final"] ) {
2020
2061
  newJSDoc([
2021
- "Creates a new subclass of class " + oClassInfo.name + " with name <code>sClassName</code>",
2062
+ "Creates a new subclass of class " + displayClassName + " with name <code>sClassName</code>",
2022
2063
  "and enriches it with the information contained in <code>oClassInfo</code>.",
2023
2064
  "",
2024
2065
  "<code>oClassInfo</code> might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.",
@@ -2304,7 +2345,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2304
2345
  "",
2305
2346
  info.enableEventBubbling ? "This event bubbles up the control hierarchy." : "",
2306
2347
  "",
2307
- "@name " + oClassInfo.name + "#" + n,
2348
+ "@name " + jsdocClassName + "#" + n,
2308
2349
  "@event",
2309
2350
  devStateTags,
2310
2351
  "@param {sap.ui.base.Event} oControlEvent",
@@ -2321,10 +2362,10 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2321
2362
  newJSDoc(lines);
2322
2363
 
2323
2364
  newJSDoc([
2324
- "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2365
+ "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + displayClassName + "</code>.",
2325
2366
  "",
2326
2367
  "When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code> if specified, ",
2327
- "otherwise it will be bound to this <code>" + oClassInfo.name + "</code> itself.",
2368
+ "otherwise it will be bound to this <code>" + displayClassName + "</code> itself.",
2328
2369
  "",
2329
2370
  !newStyle && info.doc ? info.doc : "",
2330
2371
  "",
@@ -2333,7 +2374,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2333
2374
  "@param {function(sap.ui.base.Event):void}",
2334
2375
  " fnFunction The function to be called when the event occurs",
2335
2376
  "@param {object}",
2336
- " [oListener] Context object to call the event handler with. Defaults to this <code>" + oClassInfo.name + "</code> itself",
2377
+ " [oListener] Context object to call the event handler with. Defaults to this <code>" + displayClassName + "</code> itself",
2337
2378
  "",
2338
2379
  "@returns {" + thisClass + "} Reference to <code>this</code> in order to allow method chaining",
2339
2380
  devStateTags,
@@ -2342,7 +2383,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2342
2383
  "@function"
2343
2384
  ]);
2344
2385
  newJSDoc([
2345
- "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2386
+ "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + displayClassName + "</code>.",
2346
2387
  "",
2347
2388
  "The passed function and listener object must match the ones used for event registration.",
2348
2389
  "",
@@ -2439,6 +2480,7 @@ function toRuntimeMetadataName(longname) {
2439
2480
 
2440
2481
  let isDocComment;
2441
2482
  let getLeadingCommentNode;
2483
+ let addLeadingCommentNode;
2442
2484
 
2443
2485
  // JSDoc added the node type <code>Syntax.File</code> with the same change that activated Babylon
2444
2486
  // See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869
@@ -2464,6 +2506,15 @@ if ( Syntax.File === 'File' ) {
2464
2506
  return undefined;
2465
2507
  };
2466
2508
 
2509
+ addLeadingCommentNode = function(node, comment) {
2510
+ node.leadingComments ??= [];
2511
+ let index = node.leadingComments.length;
2512
+ while (index > 0 && node.leadingComments[index - 1].type !== 'CommentBlock') {
2513
+ index--;
2514
+ }
2515
+ node.leadingComments.splice(index, 0, comment);
2516
+ }
2517
+
2467
2518
  } else {
2468
2519
 
2469
2520
  // JSDoc versions before 3.5.0
@@ -2498,6 +2549,9 @@ if ( Syntax.File === 'File' ) {
2498
2549
 
2499
2550
  return comment;
2500
2551
  };
2552
+
2553
+ // not implemented for espree (no automatic injection of @lends comments)
2554
+ addLeadingCommentNode = undefined;
2501
2555
  }
2502
2556
 
2503
2557
  //--- comment related functions that are independent from the JSdoc version
@@ -231,6 +231,7 @@ function publish(symbolSet) {
231
231
 
232
232
  // now resolve relationships
233
233
  const aRootNamespaces = createNamespaceTree(allSymbols);
234
+ pushDownDeprecations(allSymbols);
234
235
  createInheritanceTree(allSymbols);
235
236
  collectMembers(allSymbols);
236
237
  mergeEventDocumentation(allSymbols);
@@ -374,6 +375,33 @@ function makeNamespace(memberof) {
374
375
  ]);
375
376
  }
376
377
 
378
+ function pushDownDeprecations() {
379
+
380
+ function pushDown(oSymbol, sInheritedDeprecation) {
381
+ if (oSymbol == null || !isFirstClassSymbol(oSymbol)) {
382
+ return;
383
+ }
384
+ const sOwnDeprecation = oSymbol.deprecated;
385
+ if (sOwnDeprecation == null && sInheritedDeprecation != null) {
386
+ oSymbol.deprecated = sInheritedDeprecation;
387
+ info(`mark ${oSymbol.longname} as deprecated (${sInheritedDeprecation})`);
388
+ } else if (sOwnDeprecation != null) {
389
+ const info = extractSince(sOwnDeprecation);
390
+ const sinceBecause = info.since ? `As of version ${info.since}, because`: `Because`;
391
+ sInheritedDeprecation = `${sinceBecause} it is part of the deprecated package ${oSymbol.longname}`;
392
+ }
393
+ oSymbol.__ui5.children?.forEach((oChild) => pushDown(oChild, sInheritedDeprecation));
394
+ }
395
+
396
+ // w/o a library name we can't start with the library namespace
397
+ if (!templateConf.uilib) {
398
+ return;
399
+ }
400
+
401
+ // start the push down at the library namespace to ensure locality
402
+ pushDown(lookup(templateConf.uilib));
403
+ }
404
+
377
405
  //---- inheritance hierarchy ----------------------------------------------------------------------------
378
406
 
379
407
  /*
@@ -83,8 +83,8 @@ async function nonAsciiEscaper({resources, options: {encoding}}) {
83
83
  // only modify the resource's string if it was changed
84
84
  if (escaped.modified) {
85
85
  resource.setString(escaped.string);
86
+ return resource;
86
87
  }
87
- return resource;
88
88
  }
89
89
 
90
90
  return Promise.all(resources.map(processResource));
@@ -21,9 +21,10 @@ export default function({resources, options: {pattern, replacement}}) {
21
21
  return Promise.all(resources.map(async (resource) => {
22
22
  const content = await resource.getString();
23
23
  const newContent = content.replaceAll(pattern, replacement);
24
+ // only modify the resource's string if it was changed
24
25
  if (content !== newContent) {
25
26
  resource.setString(newContent);
27
+ return resource;
26
28
  }
27
- return resource;
28
29
  }));
29
30
  }
@@ -192,7 +192,7 @@ export default async function({
192
192
  }
193
193
 
194
194
  let processedResources;
195
- const useWorkers = !!taskUtil;
195
+ const useWorkers = !process.env.UI5_CLI_NO_WORKERS && !!taskUtil;
196
196
  if (useWorkers) {
197
197
  const threadMessageHandler = new FsMainThreadInterface(fsInterface(combo));
198
198
 
@@ -95,7 +95,9 @@ export default async function({workspace, taskUtil, options = {}}) {
95
95
 
96
96
  log.verbose("Collecting flexibility changes");
97
97
  const allResources = await workspace.byGlob(
98
- `${pathPrefix}/changes/*.{change,variant,ctrl_variant,ctrl_variant_change,ctrl_variant_management_change}`);
98
+ pathPrefix + "/changes/*.{annotation_change,change,variant,ctrl_variant," +
99
+ "ctrl_variant_change,ctrl_variant_management_change}"
100
+ );
99
101
 
100
102
  let bBundleCreated = false;
101
103
 
@@ -14,17 +14,24 @@ import nonAsciiEscaper from "../processors/nonAsciiEscaper.js";
14
14
  *
15
15
  * @param {object} parameters Parameters
16
16
  * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
17
+ * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
18
+ * This is only set if a cache is used and changes have been detected.
17
19
  * @param {object} parameters.options Options
18
20
  * @param {string} parameters.options.pattern Glob pattern to locate the files to be processed
19
21
  * @param {string} parameters.options.encoding source file encoding either "UTF-8" or "ISO-8859-1"
20
22
  * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
21
23
  */
22
- export default async function({workspace, options: {pattern, encoding}}) {
24
+ export default async function({workspace, changedProjectResourcePaths, options: {pattern, encoding}}) {
23
25
  if (!encoding) {
24
26
  throw new Error("[escapeNonAsciiCharacters] Mandatory option 'encoding' not provided");
25
27
  }
26
28
 
27
- const allResources = await workspace.byGlob(pattern);
29
+ let allResources;
30
+ if (changedProjectResourcePaths) {
31
+ allResources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
32
+ } else {
33
+ allResources = await workspace.byGlob(pattern);
34
+ }
28
35
 
29
36
  const processedResources = await nonAsciiEscaper({
30
37
  resources: allResources,
@@ -33,5 +40,5 @@ export default async function({workspace, options: {pattern, encoding}}) {
33
40
  }
34
41
  });
35
42
 
36
- await Promise.all(processedResources.map((resource) => workspace.write(resource)));
43
+ await Promise.all(processedResources.map((resource) => resource && workspace.write(resource)));
37
44
  }
@@ -55,7 +55,7 @@ function getCreatorOptions(projectName) {
55
55
  * Therefore it is also not supported in combination with self-contained build.
56
56
  * </p>
57
57
  *
58
- * <caption>sample resources.json</caption>
58
+ * <i>sample resources.json</i>
59
59
  *
60
60
  * ```js
61
61
  * const resourcesJson = {
@@ -16,6 +16,8 @@ import fsInterface from "@ui5/fs/fsInterface";
16
16
  * @param {object} parameters Parameters
17
17
  * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
18
18
  * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil
19
+ * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
20
+ * This is only set if a cache is used and changes have been detected.
19
21
  * @param {object} parameters.options Options
20
22
  * @param {string} parameters.options.pattern Pattern to locate the files to be processed
21
23
  * @param {boolean} [parameters.options.omitSourceMapResources=false] Whether source map resources shall
@@ -26,9 +28,24 @@ import fsInterface from "@ui5/fs/fsInterface";
26
28
  * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
27
29
  */
28
30
  export default async function({
29
- workspace, taskUtil, options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true
30
- }}) {
31
- const resources = await workspace.byGlob(pattern);
31
+ workspace, taskUtil, changedProjectResourcePaths,
32
+ options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true}
33
+ }) {
34
+ let resources;
35
+ if (changedProjectResourcePaths) {
36
+ resources = await Promise.all(
37
+ changedProjectResourcePaths
38
+ // Filtering out non-JS resources such as .map files
39
+ // FIXME: The changed resources should rather be matched against the provided pattern
40
+ .filter((resourcePath) => resourcePath.endsWith(".js"))
41
+ .map((resource) => workspace.byPath(resource))
42
+ );
43
+ } else {
44
+ resources = await workspace.byGlob(pattern);
45
+ }
46
+ if (resources.length === 0) {
47
+ return;
48
+ }
32
49
  const processedResources = await minifier({
33
50
  resources,
34
51
  fs: fsInterface(workspace),
@@ -36,7 +53,7 @@ export default async function({
36
53
  options: {
37
54
  addSourceMappingUrl: !omitSourceMapResources,
38
55
  readSourceMappingUrl: !!useInputSourceMaps,
39
- useWorkers: !!taskUtil,
56
+ useWorkers: !process.env.UI5_CLI_NO_WORKERS && !!taskUtil,
40
57
  }
41
58
  });
42
59
 
@@ -28,26 +28,30 @@ function getTimestamp() {
28
28
  *
29
29
  * @param {object} parameters Parameters
30
30
  * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
31
+ * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
32
+ * This is only set if a cache is used and changes have been detected.
31
33
  * @param {object} parameters.options Options
32
34
  * @param {string} parameters.options.pattern Pattern to locate the files to be processed
33
35
  * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
34
36
  */
35
- export default function({workspace, options: {pattern}}) {
37
+ export default async function({workspace, changedProjectResourcePaths, options: {pattern}}) {
38
+ let resources;
39
+ if (changedProjectResourcePaths) {
40
+ resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
41
+ } else {
42
+ resources = await workspace.byGlob(pattern);
43
+ }
36
44
  const timestamp = getTimestamp();
37
-
38
- return workspace.byGlob(pattern)
39
- .then((processedResources) => {
40
- return stringReplacer({
41
- resources: processedResources,
42
- options: {
43
- pattern: "${buildtime}",
44
- replacement: timestamp
45
- }
46
- });
47
- })
48
- .then((processedResources) => {
49
- return Promise.all(processedResources.map((resource) => {
50
- return workspace.write(resource);
51
- }));
52
- });
45
+ const processedResources = await stringReplacer({
46
+ resources,
47
+ options: {
48
+ pattern: "${buildtime}",
49
+ replacement: timestamp
50
+ }
51
+ });
52
+ return Promise.all(processedResources.map((resource) => {
53
+ if (resource) {
54
+ return workspace.write(resource);
55
+ }
56
+ }));
53
57
  }
@@ -24,32 +24,38 @@ import stringReplacer from "../processors/stringReplacer.js";
24
24
  *
25
25
  * @param {object} parameters Parameters
26
26
  * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
27
+ * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
28
+ * This is only set if a cache is used and changes have been detected.
27
29
  * @param {object} parameters.options Options
28
30
  * @param {string} parameters.options.copyright Replacement copyright
29
31
  * @param {string} parameters.options.pattern Pattern to locate the files to be processed
30
32
  * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
31
33
  */
32
- export default function({workspace, options: {copyright, pattern}}) {
34
+ export default async function({workspace, changedProjectResourcePaths, options: {copyright, pattern}}) {
33
35
  if (!copyright) {
34
- return Promise.resolve();
36
+ return;
35
37
  }
36
38
 
37
39
  // Replace optional placeholder ${currentYear} with the current year
38
40
  copyright = copyright.replace(/(?:\$\{currentYear\})/, new Date().getFullYear());
39
41
 
40
- return workspace.byGlob(pattern)
41
- .then((processedResources) => {
42
- return stringReplacer({
43
- resources: processedResources,
44
- options: {
45
- pattern: /(?:\$\{copyright\}|@copyright@)/g,
46
- replacement: copyright
47
- }
48
- });
49
- })
50
- .then((processedResources) => {
51
- return Promise.all(processedResources.map((resource) => {
52
- return workspace.write(resource);
53
- }));
54
- });
42
+ let resources;
43
+ if (changedProjectResourcePaths) {
44
+ resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
45
+ } else {
46
+ resources = await workspace.byGlob(pattern);
47
+ }
48
+
49
+ const processedResources = await stringReplacer({
50
+ resources,
51
+ options: {
52
+ pattern: /(?:\$\{copyright\}|@copyright@)/g,
53
+ replacement: copyright
54
+ }
55
+ });
56
+ return Promise.all(processedResources.map((resource) => {
57
+ if (resource) {
58
+ return workspace.write(resource);
59
+ }
60
+ }));
55
61
  }
@@ -14,25 +14,30 @@ import stringReplacer from "../processors/stringReplacer.js";
14
14
  *
15
15
  * @param {object} parameters Parameters
16
16
  * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files
17
+ * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project.
18
+ * This is only set if a cache is used and changes have been detected.
17
19
  * @param {object} parameters.options Options
18
20
  * @param {string} parameters.options.pattern Pattern to locate the files to be processed
19
21
  * @param {string} parameters.options.version Replacement version
20
22
  * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
21
23
  */
22
- export default function({workspace, options: {pattern, version}}) {
23
- return workspace.byGlob(pattern)
24
- .then((allResources) => {
25
- return stringReplacer({
26
- resources: allResources,
27
- options: {
28
- pattern: /\$\{(?:project\.)?version\}/g,
29
- replacement: version
30
- }
31
- });
32
- })
33
- .then((processedResources) => {
34
- return Promise.all(processedResources.map((resource) => {
35
- return workspace.write(resource);
36
- }));
37
- });
24
+ export default async function({workspace, changedProjectResourcePaths, options: {pattern, version}}) {
25
+ let resources;
26
+ if (changedProjectResourcePaths) {
27
+ resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource)));
28
+ } else {
29
+ resources = await workspace.byGlob(pattern);
30
+ }
31
+ const processedResources = await stringReplacer({
32
+ resources,
33
+ options: {
34
+ pattern: /\$\{(?:project\.)?version\}/g,
35
+ replacement: version
36
+ }
37
+ });
38
+ await Promise.all(processedResources.map((resource) => {
39
+ if (resource) {
40
+ return workspace.write(resource);
41
+ }
42
+ }));
38
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ui5/builder",
3
- "version": "5.0.0-alpha.3",
3
+ "version": "5.0.0-alpha.5",
4
4
  "description": "UI5 CLI - Builder",
5
5
  "author": {
6
6
  "name": "SAP SE",
@@ -91,32 +91,32 @@
91
91
  },
92
92
  "dependencies": {
93
93
  "@jridgewell/sourcemap-codec": "^1.5.5",
94
- "@ui5/fs": "^5.0.0-alpha.3",
95
- "@ui5/logger": "^5.0.0-alpha.3",
94
+ "@ui5/fs": "^5.0.0-alpha.5",
95
+ "@ui5/logger": "^5.0.0-alpha.5",
96
96
  "cheerio": "1.0.0",
97
97
  "escape-unicode": "^0.3.0",
98
98
  "escope": "^4.0.0",
99
- "espree": "^10.4.0",
99
+ "espree": "^11.2.0",
100
100
  "graceful-fs": "^4.2.11",
101
- "jsdoc": "^4.0.4",
101
+ "jsdoc": "^4.0.5",
102
102
  "less-openui5": "^0.11.6",
103
103
  "pretty-data": "^0.40.0",
104
- "semver": "^7.7.2",
105
- "terser": "^5.44.0",
106
- "workerpool": "^10.0.1",
104
+ "semver": "^7.8.5",
105
+ "terser": "^5.48.0",
106
+ "workerpool": "^10.0.2",
107
107
  "xml2js": "^0.6.2"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@istanbuljs/esm-loader-hook": "^0.3.0",
111
111
  "@jridgewell/trace-mapping": "^0.3.31",
112
- "@ui5/project": "^5.0.0-alpha.3",
112
+ "@ui5/project": "^5.0.0-alpha.5",
113
113
  "ava": "^6.4.1",
114
- "cross-env": "^7.0.3",
115
- "eslint": "^9.36.0",
116
- "esmock": "^2.7.3",
114
+ "cross-env": "^10.1.0",
115
+ "eslint": "^10.5.0",
116
+ "esmock": "^2.7.6",
117
117
  "line-column": "^1.0.2",
118
- "nyc": "^17.1.0",
119
- "rimraf": "^6.0.1",
120
- "sinon": "^21.0.0"
118
+ "nyc": "^18.0.0",
119
+ "rimraf": "^6.1.3",
120
+ "sinon": "^21.1.2"
121
121
  }
122
122
  }