generate-typescript-docs.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /* eslint-disable no-console */
  2. import fs from 'fs-extra';
  3. import klawSync from 'klaw-sync';
  4. import path, { extname } from 'path';
  5. import { deleteGeneratedDocs, normalizeForUrlPart } from './docgen-utils';
  6. import { TypeMap } from './typescript-docgen-types';
  7. import { TypescriptDocsParser } from './typescript-docs-parser';
  8. import { TypescriptDocsRenderer } from './typescript-docs-renderer';
  9. interface DocsSectionConfig {
  10. sourceDirs: string[];
  11. exclude?: RegExp[];
  12. outputPath: string;
  13. }
  14. const sections: DocsSectionConfig[] = [
  15. {
  16. sourceDirs: ['packages/job-queue-plugin/src/'],
  17. outputPath: '',
  18. },
  19. {
  20. sourceDirs: ['packages/core/src/', 'packages/common/src/', 'packages/testing/src/'],
  21. exclude: [/generated-shop-types/],
  22. outputPath: 'typescript-api',
  23. },
  24. {
  25. sourceDirs: ['packages/admin-ui-plugin/src/'],
  26. outputPath: '',
  27. },
  28. {
  29. sourceDirs: ['packages/asset-server-plugin/src/'],
  30. outputPath: '',
  31. },
  32. {
  33. sourceDirs: ['packages/email-plugin/src/'],
  34. outputPath: '',
  35. },
  36. {
  37. sourceDirs: ['packages/elasticsearch-plugin/src/'],
  38. outputPath: '',
  39. },
  40. {
  41. sourceDirs: ['packages/payments-plugin/src/'],
  42. exclude: [/generated-shop-types/],
  43. outputPath: '',
  44. },
  45. {
  46. sourceDirs: ['packages/harden-plugin/src/'],
  47. outputPath: '',
  48. },
  49. {
  50. sourceDirs: ['packages/stellate-plugin/src/'],
  51. outputPath: '',
  52. },
  53. {
  54. sourceDirs: ['packages/sentry-plugin/src/'],
  55. outputPath: '',
  56. },
  57. {
  58. sourceDirs: ['packages/graphiql-plugin/src/'],
  59. outputPath: '',
  60. },
  61. {
  62. sourceDirs: ['packages/telemetry-plugin/src/'],
  63. outputPath: '',
  64. },
  65. {
  66. sourceDirs: ['packages/admin-ui/src/lib/', 'packages/ui-devkit/src/'],
  67. exclude: [/generated-types/],
  68. outputPath: 'admin-ui-api',
  69. },
  70. {
  71. sourceDirs: ['packages/dashboard/src/'],
  72. outputPath: 'dashboard',
  73. },
  74. ];
  75. generateTypescriptDocs(sections);
  76. const watchMode = !!process.argv.find(arg => arg === '--watch' || arg === '-w');
  77. if (watchMode) {
  78. console.log(`Watching for changes to source files...`);
  79. sections.forEach(section => {
  80. section.sourceDirs.forEach(dir => {
  81. fs.watch(dir, { recursive: true }, (eventType, file) => {
  82. if (file && extname(file) === '.ts') {
  83. console.log(`Changes detected in ${dir}`);
  84. generateTypescriptDocs([section], true);
  85. }
  86. });
  87. });
  88. });
  89. }
  90. /**
  91. * Uses the TypeScript compiler API to parse the given files and extract out the documentation
  92. * into markdown files
  93. */
  94. function generateTypescriptDocs(config: DocsSectionConfig[], isWatchMode: boolean = false) {
  95. const timeStart = +new Date();
  96. // This map is used to cache types and their corresponding Hugo path. It is used to enable
  97. // hyperlinking from a member's "type" to the definition of that type.
  98. const globalTypeMap: TypeMap = new Map();
  99. if (!isWatchMode) {
  100. for (const { outputPath, sourceDirs } of config) {
  101. deleteGeneratedDocs(absOutputPath(outputPath));
  102. }
  103. }
  104. for (const { outputPath, sourceDirs, exclude } of config) {
  105. const sourceFilePaths = getSourceFilePaths(sourceDirs, exclude);
  106. const docsPages = new TypescriptDocsParser().parse(sourceFilePaths);
  107. for (const page of docsPages) {
  108. const { category, fileName, declarations } = page;
  109. for (const declaration of declarations) {
  110. const pathToTypeDoc = `reference/${outputPath ? `${outputPath}/` : ''}${
  111. category ? category.map(part => normalizeForUrlPart(part)).join('/') + '/' : ''
  112. }${fileName === 'index' ? '' : fileName}#${toHash(declaration.title)}`;
  113. globalTypeMap.set(declaration.title, pathToTypeDoc);
  114. }
  115. }
  116. const docsUrl = ``;
  117. const generatedCount = new TypescriptDocsRenderer().render(
  118. docsPages,
  119. docsUrl,
  120. absOutputPath(outputPath),
  121. globalTypeMap,
  122. );
  123. if (generatedCount) {
  124. console.log(
  125. `Generated ${generatedCount} typescript api docs for "${outputPath}" in ${
  126. +new Date() - timeStart
  127. }ms`,
  128. );
  129. }
  130. }
  131. }
  132. function toHash(title: string): string {
  133. return title.replace(/\s/g, '').toLowerCase();
  134. }
  135. function absOutputPath(outputPath: string): string {
  136. return path.join(__dirname, '../../docs/docs/reference/', outputPath);
  137. }
  138. function getSourceFilePaths(sourceDirs: string[], excludePatterns: RegExp[] = []): string[] {
  139. return sourceDirs
  140. .map(scanPath =>
  141. klawSync(path.join(__dirname, '../../', scanPath), {
  142. nodir: true,
  143. filter: item => {
  144. const ext = path.extname(item.path);
  145. if (ext === '.ts' || ext === '.tsx') {
  146. for (const pattern of excludePatterns) {
  147. if (pattern.test(item.path)) {
  148. return false;
  149. }
  150. }
  151. return true;
  152. }
  153. return false;
  154. },
  155. traverseAll: true,
  156. }),
  157. )
  158. .reduce((allFiles, files) => [...allFiles, ...files], [])
  159. .map(item => item.path);
  160. }