builder/lib/processors/jsdoc/jsdocGenerator.js

  1. import {spawn} from "node:child_process";
  2. import fs from "graceful-fs";
  3. import path from "node:path";
  4. import {promisify} from "node:util";
  5. const writeFile = promisify(fs.writeFile);
  6. import {createAdapter} from "@ui5/fs/resourceFactory";
  7. import {fileURLToPath} from "node:url";
  8. /**
  9. * @public
  10. * @module @ui5/builder/processors/jsdoc/jsdocGenerator
  11. */
  12. /**
  13. * JSDoc generator
  14. *
  15. * @public
  16. * @function default
  17. * @static
  18. *
  19. * @param {object} parameters Parameters
  20. * @param {string} parameters.sourcePath Path of the source files to be processed
  21. * @param {string} parameters.targetPath Path to write any output files
  22. * @param {string} parameters.tmpPath Path to write temporary and debug files
  23. * @param {object} parameters.options Options
  24. * @param {string} parameters.options.projectName Project name
  25. * @param {string} parameters.options.namespace Namespace to build (e.g. <code>some/project/name</code>)
  26. * @param {string} parameters.options.version Project version
  27. * @param {Array} [parameters.options.variants=["apijson"]] JSDoc variants to be built
  28. * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with newly created resources
  29. */
  30. export default async function jsdocGenerator(
  31. {sourcePath, targetPath, tmpPath, options: {projectName, namespace, version, variants}} = {}
  32. ) {
  33. if (!sourcePath || !targetPath || !tmpPath || !projectName || !namespace || !version) {
  34. throw new Error("[jsdocGenerator]: One or more mandatory parameters not provided");
  35. }
  36. if (!variants || variants.length === 0) {
  37. variants = ["apijson"];
  38. }
  39. const config = await jsdocGenerator._generateJsdocConfig({
  40. targetPath,
  41. tmpPath,
  42. namespace,
  43. projectName,
  44. version,
  45. variants
  46. });
  47. const configPath = await jsdocGenerator._writeJsdocConfig(tmpPath, config);
  48. await jsdocGenerator._buildJsdoc({
  49. sourcePath,
  50. configPath
  51. });
  52. const fsTarget = createAdapter({
  53. fsBasePath: targetPath,
  54. virBasePath: "/"
  55. });
  56. // create resources from the output files
  57. return Promise.all([
  58. fsTarget.byPath(`/test-resources/${namespace}/designtime/api.json`)
  59. // fsTarget.byPath(`/libraries/${options.projectName}.js`)
  60. ]).then((res) => res.filter(($)=>$));
  61. }
  62. /**
  63. * Generate jsdoc-config.json content
  64. *
  65. * @private
  66. * @param {object} parameters Parameters
  67. * @param {string} parameters.targetPath Path to write any output files
  68. * @param {string} parameters.tmpPath Path to write temporary and debug files
  69. * @param {string} parameters.projectName Project name
  70. * @param {string} parameters.version Project version
  71. * @param {string} parameters.namespace Namespace to use (e.g. <code>some/project/name</code>)
  72. * @param {Array} parameters.variants JSDoc variants to be built
  73. * @returns {string} jsdoc-config.json content string
  74. */
  75. async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName, version, variants}) {
  76. // Backlash needs to be escaped as double-backslash
  77. // This is not only relevant for win32 paths but also for
  78. // Unix directory names that contain a backslash in their name
  79. const backslashRegex = /\\/g;
  80. // Resolve path to this script to get the path to the JSDoc extensions folder
  81. const jsdocPath = path.normalize(import.meta.dirname);
  82. const pluginPath = path.join(jsdocPath, "lib", "ui5", "plugin.js").replace(backslashRegex, "\\\\");
  83. const templatePath = path.join(jsdocPath, "lib", "ui5", "template").replace(backslashRegex, "\\\\");
  84. const destinationPath = path.normalize(tmpPath).replace(backslashRegex, "\\\\");
  85. const jsapiFilePath = path.join(targetPath, "libraries", projectName + ".js").replace(backslashRegex, "\\\\");
  86. const apiJsonFolderPath = path.join(tmpPath, "dependency-apis").replace(backslashRegex, "\\\\");
  87. const apiJsonFilePath =
  88. path.join(targetPath, "test-resources", path.normalize(namespace), "designtime", "api.json")
  89. .replace(backslashRegex, "\\\\");
  90. // Note: While the projectName could also be used here, it is not ensured that it fits to
  91. // the library namespace.
  92. // As the "uilib" information is used to check for certain constraints it must be aligned with
  93. // the technical namespace that is used in the folder-structure, library.js and for the
  94. // sap.ui.base.Object based classes.
  95. const uilib = namespace.replace(/\//g, ".");
  96. const config = `{
  97. "plugins": ["${pluginPath}"],
  98. "opts": {
  99. "recurse": true,
  100. "lenient": true,
  101. "template": "${templatePath}",
  102. "ui5": {
  103. "saveSymbols": true
  104. },
  105. "destination": "${destinationPath}"
  106. },
  107. "templates": {
  108. "ui5": {
  109. "variants": ${JSON.stringify(variants)},
  110. "version": "${version}",
  111. "uilib": "${uilib}",
  112. "jsapiFile": "${jsapiFilePath}",
  113. "apiJsonFolder": "${apiJsonFolderPath}",
  114. "apiJsonFile": "${apiJsonFilePath}"
  115. }
  116. }
  117. }`;
  118. return config;
  119. }
  120. /**
  121. * Write jsdoc-config.json to file system
  122. *
  123. * @private
  124. * @param {string} targetDirPath Directory Path to write the jsdoc-config.json file to
  125. * @param {string} config jsdoc-config.json content
  126. * @returns {string} Full path to the written jsdoc-config.json file
  127. */
  128. async function writeJsdocConfig(targetDirPath, config) {
  129. const configPath = path.join(targetDirPath, "jsdoc-config.json");
  130. await writeFile(configPath, config);
  131. return configPath;
  132. }
  133. /**
  134. * Execute JSDoc build by spawning JSDoc as an external process
  135. *
  136. * @private
  137. * @param {object} parameters Parameters
  138. * @param {string} parameters.sourcePath Project resources (input for JSDoc generation)
  139. * @param {string} parameters.configPath Full path to jsdoc-config.json file
  140. * @returns {Promise<undefined>}
  141. */
  142. async function buildJsdoc({sourcePath, configPath}) {
  143. const args = [
  144. fileURLToPath(import.meta.resolve("jsdoc/jsdoc.js")),
  145. "-c",
  146. configPath,
  147. "--verbose",
  148. sourcePath
  149. ];
  150. const exitCode = await new Promise((resolve /* , reject */) => {
  151. const child = spawn("node", args, {
  152. stdio: ["ignore", "ignore", "inherit"]
  153. });
  154. child.on("close", resolve);
  155. });
  156. if (exitCode !== 0) {
  157. throw new Error(`JSDoc reported an error, check the log for issues (exit code: ${exitCode})`);
  158. }
  159. }
  160. jsdocGenerator._generateJsdocConfig = generateJsdocConfig;
  161. jsdocGenerator._writeJsdocConfig = writeJsdocConfig;
  162. jsdocGenerator._buildJsdoc = buildJsdoc;