builder/lib/tasks/jsdoc/generateJsdoc.js

  1. const log = require("@ui5/logger").getLogger("builder:tasks:jsdoc:generateJsdoc");
  2. const path = require("path");
  3. const makeDir = require("make-dir");
  4. const os = require("os");
  5. const fs = require("graceful-fs");
  6. const {promisify} = require("util");
  7. const mkdtemp = promisify(fs.mkdtemp);
  8. const rimraf = promisify(require("rimraf"));
  9. const jsdocGenerator = require("../../processors/jsdoc/jsdocGenerator");
  10. const {resourceFactory} = require("@ui5/fs");
  11. /**
  12. *
  13. * @public
  14. * @typedef {object} GenerateJsdocOptions
  15. * @property {string|string[]} pattern Pattern to locate the files to be processed
  16. * @property {string} projectName Project name
  17. * @property {string} namespace Namespace to build (e.g. <code>some/project/name</code>)
  18. * @property {string} version Project version
  19. */
  20. /**
  21. * Task to execute a JSDoc build for UI5 projects
  22. *
  23. * @public
  24. * @alias module:@ui5/builder.tasks.generateJsdoc
  25. * @param {object} parameters Parameters
  26. * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
  27. * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
  28. * @param {GenerateJsdocOptions} parameters.options Options
  29. * @param {module:@ui5/builder.tasks.TaskUtil|object} [parameters.taskUtil] TaskUtil
  30. * @param {object} [parameters.buildContext] Internal, deprecated parameter
  31. * @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
  32. */
  33. const generateJsdoc = async function({
  34. taskUtil,
  35. buildContext,
  36. workspace,
  37. dependencies,
  38. options = {}
  39. }) {
  40. const {projectName, namespace, version, pattern} = /** @type {GenerateJsdocOptions} */ (options);
  41. if (!projectName || !namespace || !version || !pattern) {
  42. throw new Error("[generateJsdoc]: One or more mandatory options not provided");
  43. }
  44. const {sourcePath: resourcePath, targetPath, tmpPath, cleanup} =
  45. await generateJsdoc._createTmpDirs(projectName);
  46. // TODO 3.0: remove buildContext
  47. const _taskUtil = taskUtil || buildContext;
  48. if (_taskUtil) {
  49. _taskUtil.registerCleanupTask(cleanup);
  50. }
  51. const [writtenResourcesCount] = await Promise.all([
  52. generateJsdoc._writeResourcesToDir({
  53. workspace,
  54. pattern,
  55. targetPath: resourcePath
  56. }),
  57. generateJsdoc._writeDependencyApisToDir({
  58. dependencies,
  59. targetPath: path.join(tmpPath, "dependency-apis")
  60. })
  61. ]);
  62. if (writtenResourcesCount === 0) {
  63. log.info(`Failed to find any input resources for project ${projectName} using pattern ` +
  64. `${pattern}. Skipping JSDoc generation...`);
  65. return;
  66. }
  67. const createdResources = await jsdocGenerator({
  68. sourcePath: resourcePath,
  69. targetPath,
  70. tmpPath,
  71. options: {
  72. projectName,
  73. namespace,
  74. version,
  75. variants: ["apijson"]
  76. }
  77. });
  78. await Promise.all(createdResources.map((resource) => {
  79. return workspace.write(resource);
  80. }));
  81. };
  82. /**
  83. * Create temporary directories for JSDoc generation processor
  84. *
  85. * @private
  86. * @param {string} projectName Project name used for naming the temporary working directory
  87. * @returns {Promise<object>} Promise resolving with sourcePath, targetPath and tmpPath strings
  88. */
  89. async function createTmpDirs(projectName) {
  90. const tmpDirPath = await generateJsdoc._createTmpDir(projectName);
  91. const sourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below
  92. await makeDir(sourcePath, {fs});
  93. const targetPath = path.join(tmpDirPath, "target"); // dir will be created by jsdoc itself
  94. await makeDir(targetPath, {fs});
  95. const tmpPath = path.join(tmpDirPath, "tmp"); // dir needs to be created by us
  96. await makeDir(tmpPath, {fs});
  97. return {
  98. sourcePath,
  99. targetPath,
  100. tmpPath,
  101. cleanup: async () => {
  102. return rimraf(tmpDirPath);
  103. }
  104. };
  105. }
  106. /**
  107. * Create a temporary directory on the host system
  108. *
  109. * @private
  110. * @param {string} projectName Project name used for naming the temporary directory
  111. * @returns {Promise<string>} Promise resolving with path of the temporary directory
  112. */
  113. async function createTmpDir(projectName) {
  114. const sanitizedProjectName = projectName.replace(/[^A-Za-z0-9]/g, "");
  115. const tmpRootPath = path.join(os.tmpdir(), "ui5-tooling");
  116. await makeDir(tmpRootPath, {fs});
  117. // Appending minus sign also because node docs advise to "avoid trailing X characters in prefix"
  118. return mkdtemp(path.join(tmpRootPath, `jsdoc-${sanitizedProjectName}-`));
  119. }
  120. /**
  121. * Write resources from workspace matching the given pattern to the given fs destination
  122. *
  123. * @private
  124. * @param {object} parameters Parameters
  125. * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
  126. * @param {string} parameters.pattern Pattern to match resources in workspace against
  127. * @param {string} parameters.targetPath Path to write the resources to
  128. * @returns {Promise<number>} Promise resolving with number of resources written to given directory
  129. */
  130. async function writeResourcesToDir({workspace, pattern, targetPath}) {
  131. const fsTarget = resourceFactory.createAdapter({
  132. fsBasePath: targetPath,
  133. virBasePath: "/resources/"
  134. });
  135. let allResources;
  136. if (workspace.byGlobSource) { // API only available on duplex collections
  137. allResources = await workspace.byGlobSource(pattern);
  138. } else {
  139. allResources = await workspace.byGlob(pattern);
  140. }
  141. // write all resources to the tmp folder
  142. await Promise.all(allResources.map((resource) => fsTarget.write(resource)));
  143. return allResources.length;
  144. }
  145. /**
  146. * Write api.json files of dependencies to given target path in a flat structure
  147. *
  148. * @private
  149. * @param {object} parameters Parameters
  150. * @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
  151. * @param {string} parameters.targetPath Path to write the resources to
  152. * @returns {Promise<number>} Promise resolving with number of resources written to given directory
  153. */
  154. async function writeDependencyApisToDir({dependencies, targetPath}) {
  155. const depApis = await dependencies.byGlob("/test-resources/**/designtime/api.json");
  156. // Clone resources before changing their path
  157. const apis = await Promise.all(depApis.map((resource) => resource.clone()));
  158. for (let i = 0; i < apis.length; i++) {
  159. apis[i].setPath(`/api-${i}.json`);
  160. }
  161. const fsTarget = resourceFactory.createAdapter({
  162. fsBasePath: targetPath,
  163. virBasePath: "/"
  164. });
  165. await Promise.all(apis.map((resource) => fsTarget.write(resource)));
  166. return apis.length;
  167. }
  168. module.exports = generateJsdoc;
  169. module.exports._createTmpDirs = createTmpDirs;
  170. module.exports._createTmpDir = createTmpDir;
  171. module.exports._writeResourcesToDir = writeResourcesToDir;
  172. module.exports._writeDependencyApisToDir = writeDependencyApisToDir;