builder/lib/processors/jsdoc/jsdocGenerator.js

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