builder/lib/tasks/generateThemeDesignerResources.js

  1. const posixPath = require("path").posix;
  2. const log = require("@ui5/logger").getLogger("builder:tasks:generateThemeDesignerResources");
  3. const libraryLessGenerator = require("../processors/libraryLessGenerator");
  4. const {ReaderCollectionPrioritized, Resource, fsInterface} = require("@ui5/fs");
  5. function generateLibraryDotTheming({namespace, version, hasThemes}) {
  6. const dotTheming = {
  7. sEntity: "Library",
  8. sId: namespace,
  9. sVersion: version
  10. };
  11. if (namespace === "sap/ui/core") {
  12. dotTheming.aFiles = [
  13. "library",
  14. "global", // Additional entry compared to UI5 root .theming
  15. "css_variables",
  16. ];
  17. }
  18. if (!hasThemes) {
  19. // Set ignore flag when there are no themes at all
  20. // This is important in case a library used to contain themes that have been removed
  21. // in a later version of the library.
  22. dotTheming.bIgnore = true;
  23. }
  24. return new Resource({
  25. path: `/resources/${namespace}/.theming`,
  26. string: JSON.stringify(dotTheming, null, 2)
  27. });
  28. }
  29. async function generateThemeDotTheming({workspace, combo, themeFolder}) {
  30. const themeName = posixPath.basename(themeFolder);
  31. const libraryMatchPattern = /^\/resources\/(.*)\/themes\/[^/]*$/i;
  32. const libraryMatch = libraryMatchPattern.exec(themeFolder);
  33. let libraryName;
  34. if (libraryMatch) {
  35. libraryName = libraryMatch[1].replace(/\//g, ".");
  36. } else {
  37. throw new Error(`Failed to extract library name from theme folder path: ${themeFolder}`);
  38. }
  39. const dotThemingTargetPath = posixPath.join(themeFolder, ".theming");
  40. if (libraryName === "sap.ui.core") {
  41. // sap.ui.core should always have a .theming file for all themes
  42. if (await workspace.byPath(dotThemingTargetPath)) {
  43. // .theming file present, skip further processing
  44. return;
  45. } else {
  46. throw new Error(`.theming file for theme ${themeName} missing in sap.ui.core library source`);
  47. }
  48. }
  49. let newDotThemingResource;
  50. const coreDotThemingResource = await combo.byPath(`/resources/sap/ui/core/themes/${themeName}/.theming`);
  51. if (coreDotThemingResource) {
  52. // Copy .theming file from core
  53. newDotThemingResource = await coreDotThemingResource.clone();
  54. newDotThemingResource.setPath(dotThemingTargetPath);
  55. } else {
  56. // No core .theming file found for this theme => Generate a .theming file
  57. const dotTheming = {
  58. sEntity: "Theme",
  59. sId: themeName,
  60. sVendor: "SAP"
  61. };
  62. if (themeName !== "base") {
  63. dotTheming.oExtends = "base";
  64. }
  65. newDotThemingResource = new Resource({
  66. path: dotThemingTargetPath,
  67. string: JSON.stringify(dotTheming, null, 2)
  68. });
  69. }
  70. return newDotThemingResource;
  71. }
  72. /**
  73. * Generates resources required for integration with the SAP Theme Designer.
  74. *
  75. * @public
  76. * @alias module:@ui5/builder.tasks.generateThemeDesignerResources
  77. * @param {object} parameters Parameters
  78. * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
  79. * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
  80. * @param {object} parameters.options Options
  81. * @param {string} parameters.options.projectName Project name
  82. * @param {string} parameters.options.version Project version
  83. * @param {string} [parameters.options.namespace] If the project is of type <code>library</code>, provide its namespace.
  84. * Omit for type <code>theme-library</code>
  85. * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
  86. */
  87. module.exports = async function({workspace, dependencies, options: {projectName, version, namespace}}) {
  88. // Skip sap.ui.documentation since it is not intended to be available in SAP Theme Designer to create custom themes
  89. if (namespace === "sap/ui/documentation") {
  90. return;
  91. }
  92. let librarySourceLessPattern;
  93. if (namespace) {
  94. // In case of a library only check for themes directly below the namespace
  95. librarySourceLessPattern = `/resources/${namespace}/themes/*/library.source.less`;
  96. } else {
  97. // In case of a theme-library check for all "themes"
  98. librarySourceLessPattern = `/resources/**/themes/*/library.source.less`;
  99. }
  100. const librarySourceLessResources = await workspace.byGlob(librarySourceLessPattern);
  101. const hasThemes = librarySourceLessResources.length > 0;
  102. // library .theming file
  103. // Only for type "library". Type "theme-library" does not provide a namespace
  104. // Also needs to be created in case a library does not have any themes (see bIgnore flag)
  105. if (namespace) {
  106. log.verbose(`Generating .theming for namespace ${namespace}`);
  107. const libraryDotThemingResource = generateLibraryDotTheming({
  108. namespace,
  109. version,
  110. hasThemes
  111. });
  112. await workspace.write(libraryDotThemingResource);
  113. }
  114. if (!hasThemes) {
  115. // Skip further processing as there are no themes
  116. return;
  117. }
  118. const combo = new ReaderCollectionPrioritized({
  119. name: `generateThemeDesignerResources - prioritize workspace over dependencies: ${projectName}`,
  120. readers: [workspace, dependencies]
  121. });
  122. // theme .theming files
  123. const themeDotThemingFiles = await Promise.all(
  124. librarySourceLessResources.map((librarySourceLess) => {
  125. const themeFolder = posixPath.dirname(librarySourceLess.getPath());
  126. log.verbose(`Generating .theming for theme ${themeFolder}`);
  127. return generateThemeDotTheming({
  128. workspace, combo, themeFolder
  129. });
  130. })
  131. );
  132. await Promise.all(
  133. themeDotThemingFiles.map(async (resource) => {
  134. if (resource) {
  135. await workspace.write(resource);
  136. }
  137. })
  138. );
  139. // library.less files
  140. const libraryLessResources = await libraryLessGenerator({
  141. resources: librarySourceLessResources,
  142. fs: fsInterface(combo),
  143. });
  144. await Promise.all(
  145. libraryLessResources.map((resource) => workspace.write(resource))
  146. );
  147. };